Bug 714733 - Instant messaging in Thunderbird, r=bienvenu,bwinton,Standard8,mconley.
authorFlorian Quèze <florian@queze.net>
Tue, 13 Mar 2012 02:14:45 +0100
changeset 9632 8cdfed92867f885fda98664395236b7829947a1d
parent 9631 4b5da7e5d0680c6617ec743109e6efc88ca413da
child 9633 f3dd079d3da97f57cce376943b24fd456675f5ec
push id7361
push userflorian@queze.net
push dateTue, 13 Mar 2012 01:15:19 +0000
treeherdercomm-central@8cdfed92867f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbienvenu, bwinton, Standard8, mconley
bugs714733
Bug 714733 - Instant messaging in Thunderbird, r=bienvenu,bwinton,Standard8,mconley.
chat/Makefile.in
chat/chat-prefs.js
chat/components/public/Makefile.in
chat/components/public/imIAccount.idl
chat/components/public/imIAccountsService.idl
chat/components/public/imICommandsService.idl
chat/components/public/imIContactsService.idl
chat/components/public/imIConversationsService.idl
chat/components/public/imICoreService.idl
chat/components/public/imILogger.idl
chat/components/public/imIStatusInfo.idl
chat/components/public/imITagsService.idl
chat/components/public/imIUserStatusInfo.idl
chat/components/public/prplIConversation.idl
chat/components/public/prplIMessage.idl
chat/components/public/prplIPref.idl
chat/components/public/prplIProtocol.idl
chat/components/public/prplIRequest.idl
chat/components/public/prplITooltipInfo.idl
chat/components/src/Makefile.in
chat/components/src/imAccounts.js
chat/components/src/imAccounts.manifest
chat/components/src/imCommands.js
chat/components/src/imCommands.manifest
chat/components/src/imContacts.js
chat/components/src/imContacts.manifest
chat/components/src/imConversations.js
chat/components/src/imConversations.manifest
chat/components/src/imCore.js
chat/components/src/imCore.manifest
chat/components/src/logger.js
chat/components/src/logger.manifest
chat/components/src/smileProtocolHandler.js
chat/components/src/smileProtocolHandler.manifest
chat/content/Makefile.in
chat/content/browserRequest.js
chat/content/browserRequest.xul
chat/content/conv.html
chat/content/convbrowser.xml
chat/content/jar.mn
chat/locales/Makefile.in
chat/locales/en-US/accounts.properties
chat/locales/en-US/commands.properties
chat/locales/en-US/conversations.properties
chat/locales/en-US/facebook.properties
chat/locales/en-US/irc.properties
chat/locales/en-US/status.properties
chat/locales/en-US/twitter.properties
chat/locales/en-US/xmpp.properties
chat/locales/jar.mn
chat/makefiles.sh
chat/modules/Makefile.in
chat/modules/hiddenWindow.jsm
chat/modules/http.jsm
chat/modules/imContentSink.jsm
chat/modules/imServices.jsm
chat/modules/imSmileys.jsm
chat/modules/imStatusUtils.jsm
chat/modules/imTextboxUtils.jsm
chat/modules/imThemes.jsm
chat/modules/imXPCOMUtils.jsm
chat/modules/jsProtoHelper.jsm
chat/modules/socket.jsm
chat/protocols/facebook/Makefile.in
chat/protocols/facebook/facebook.js
chat/protocols/facebook/facebook.manifest
chat/protocols/facebook/icons/prpl-facebook-32.png
chat/protocols/facebook/icons/prpl-facebook-48.png
chat/protocols/facebook/icons/prpl-facebook.png
chat/protocols/facebook/jar.mn
chat/protocols/gtalk/Makefile.in
chat/protocols/gtalk/gtalk.js
chat/protocols/gtalk/gtalk.manifest
chat/protocols/gtalk/icons/prpl-gtalk-32.png
chat/protocols/gtalk/icons/prpl-gtalk-48.png
chat/protocols/gtalk/icons/prpl-gtalk.png
chat/protocols/gtalk/jar.mn
chat/protocols/irc/Makefile.in
chat/protocols/irc/icons/prpl-irc-32.png
chat/protocols/irc/icons/prpl-irc-48.png
chat/protocols/irc/icons/prpl-irc.png
chat/protocols/irc/irc.js
chat/protocols/irc/irc.manifest
chat/protocols/irc/ircBase.jsm
chat/protocols/irc/ircCTCP.jsm
chat/protocols/irc/ircCommands.jsm
chat/protocols/irc/ircDCC.jsm
chat/protocols/irc/ircHandlers.jsm
chat/protocols/irc/ircISUPPORT.jsm
chat/protocols/irc/ircUtils.jsm
chat/protocols/irc/jar.mn
chat/protocols/irc/test/test_ctcpColoring.js
chat/protocols/irc/test/test_ctcpFormatting.js
chat/protocols/irc/test/xpcshell.ini
chat/protocols/jsTest/Makefile.in
chat/protocols/jsTest/jsTestProtocol.js
chat/protocols/jsTest/jsTestProtocol.manifest
chat/protocols/twitter/Makefile.in
chat/protocols/twitter/icons/prpl-twitter-32.png
chat/protocols/twitter/icons/prpl-twitter-48.png
chat/protocols/twitter/icons/prpl-twitter.png
chat/protocols/twitter/jar.mn
chat/protocols/twitter/twitter.js
chat/protocols/twitter/twitter.manifest
chat/protocols/xmpp/Makefile.in
chat/protocols/xmpp/xmpp-authmechs.jsm
chat/protocols/xmpp/xmpp-session.jsm
chat/protocols/xmpp/xmpp-xml.jsm
chat/protocols/xmpp/xmpp.js
chat/protocols/xmpp/xmpp.jsm
chat/protocols/xmpp/xmpp.manifest
chat/themes/Makefile.in
chat/themes/available-16.png
chat/themes/available.png
chat/themes/away-16.png
chat/themes/away.png
chat/themes/browserRequest.css
chat/themes/chat-16.png
chat/themes/chat-left-16.png
chat/themes/conv.css
chat/themes/icons/insecure.png
chat/themes/icons/prpl-generic-32.png
chat/themes/icons/prpl-generic-48.png
chat/themes/icons/prpl-generic.png
chat/themes/icons/prpl-unknown-32.png
chat/themes/icons/prpl-unknown-48.png
chat/themes/icons/prpl-unknown.png
chat/themes/icons/secure.png
chat/themes/idle-16.png
chat/themes/idle.png
chat/themes/jar.mn
chat/themes/mobile-16.png
chat/themes/mobile.png
chat/themes/offline-16.png
chat/themes/offline.png
chat/themes/typed-16.png
chat/themes/typing-16.png
chat/themes/unknown-16.png
chat/themes/unknown.png
mail/app/profile/all-thunderbird.js
mail/base/content/folderPane.js
mail/base/content/glodaFacetBindings.xml
mail/base/content/glodaFacetView.css
mail/base/content/glodaFacetView.js
mail/base/content/hiddenWindow.xul
mail/base/content/mailCore.js
mail/base/content/mailWindowOverlay.xul
mail/base/modules/mailMigrator.js
mail/build.mk
mail/components/Makefile.in
mail/components/im/Makefile.in
mail/components/im/all-im.js
mail/components/im/content/addbuddy.js
mail/components/im/content/addbuddy.xul
mail/components/im/content/am-im.js
mail/components/im/content/am-im.xul
mail/components/im/content/chat-messenger-overlay.js
mail/components/im/content/chat-messenger-overlay.xul
mail/components/im/content/chat.css
mail/components/im/content/imAccount.xml
mail/components/im/content/imAccountWizard.js
mail/components/im/content/imAccountWizard.xul
mail/components/im/content/imAccounts.css
mail/components/im/content/imAccounts.js
mail/components/im/content/imAccounts.xul
mail/components/im/content/imContextMenu.js
mail/components/im/content/imStatusSelector.js
mail/components/im/content/imbuddytooltip.xml
mail/components/im/content/imcontact.xml
mail/components/im/content/imconv.xml
mail/components/im/content/imconversation.xml
mail/components/im/content/imgroup.xml
mail/components/im/content/imsearch.xml
mail/components/im/content/joinchat.js
mail/components/im/content/joinchat.xul
mail/components/im/im.manifest
mail/components/im/imIncomingServer.js
mail/components/im/imProtocolInfo.js
mail/components/im/jar.mn
mail/components/im/messages/Bitmaps/minus-hover.png
mail/components/im/messages/Bitmaps/minus.png
mail/components/im/messages/Bitmaps/plus-hover.png
mail/components/im/messages/Bitmaps/plus.png
mail/components/im/messages/Footer.html
mail/components/im/messages/Incoming/Content.html
mail/components/im/messages/Incoming/Context.html
mail/components/im/messages/Incoming/NextContent.html
mail/components/im/messages/Info.plist
mail/components/im/messages/NextStatus.html
mail/components/im/messages/Status.html
mail/components/im/messages/main.css
mail/components/im/modules/index_im.js
mail/components/im/modules/search_im.js
mail/components/im/smileys/angry.png
mail/components/im/smileys/confused.png
mail/components/im/smileys/cool.png
mail/components/im/smileys/cry.png
mail/components/im/smileys/embarrassed.png
mail/components/im/smileys/grin.png
mail/components/im/smileys/heart.png
mail/components/im/smileys/manga_annoyed.png
mail/components/im/smileys/manga_embarrassed.png
mail/components/im/smileys/manga_smile.png
mail/components/im/smileys/manga_stunned.png
mail/components/im/smileys/manga_tired.png
mail/components/im/smileys/sad.png
mail/components/im/smileys/shocked.png
mail/components/im/smileys/slant.png
mail/components/im/smileys/slant2.png
mail/components/im/smileys/smile.png
mail/components/im/smileys/sp_laugh.png
mail/components/im/smileys/straight_face.png
mail/components/im/smileys/theme.js
mail/components/im/smileys/tongue.png
mail/components/im/smileys/wink.png
mail/components/im/themes/chat.css
mail/components/im/themes/founder.png
mail/components/im/themes/half-operator.png
mail/components/im/themes/imAccountWizard.css
mail/components/im/themes/imAccounts.css
mail/components/im/themes/imBuddytooltip.css
mail/components/im/themes/imMenulist.css
mail/components/im/themes/imRichlistbox.css
mail/components/im/themes/imStatus.css
mail/components/im/themes/operator.png
mail/components/im/themes/voice.png
mail/components/preferences/chat.js
mail/components/preferences/chat.xul
mail/components/preferences/jar.mn
mail/components/preferences/preferences.xul
mail/installer/package-manifest.in
mail/locales/Makefile.in
mail/locales/en-US/chrome/messenger/AccountManager.dtd
mail/locales/en-US/chrome/messenger/addbuddy.dtd
mail/locales/en-US/chrome/messenger/am-im.dtd
mail/locales/en-US/chrome/messenger/chat.dtd
mail/locales/en-US/chrome/messenger/chat.properties
mail/locales/en-US/chrome/messenger/imAccountWizard.dtd
mail/locales/en-US/chrome/messenger/imAccounts.dtd
mail/locales/en-US/chrome/messenger/imAccounts.properties
mail/locales/en-US/chrome/messenger/joinChat.dtd
mail/locales/en-US/chrome/messenger/messenger.dtd
mail/locales/en-US/chrome/messenger/preferences/chat.dtd
mail/locales/en-US/chrome/messenger/preferences/preferences.dtd
mail/locales/filter.py
mail/locales/jar.mn
mail/locales/l10n-aurora.ini
mail/locales/l10n-beta.ini
mail/locales/l10n-central.ini
mail/locales/l10n.ini
mail/makefiles.sh
mail/test/mozmill/migration/test-toolbar.js
mail/test/xpcshell.ini
mail/themes/gnomestripe/jar.mn
mail/themes/gnomestripe/mail/chat.css
mail/themes/gnomestripe/mail/icons/chat-toolbar-small.png
mail/themes/gnomestripe/mail/icons/chat-toolbar.png
mail/themes/gnomestripe/mail/icons/mail-toolbar-small.png
mail/themes/gnomestripe/mail/icons/mail-toolbar.png
mail/themes/gnomestripe/mail/icons/status-small.png
mail/themes/gnomestripe/mail/icons/status.png
mail/themes/gnomestripe/mail/preferences/chat.png
mail/themes/gnomestripe/mail/preferences/preferences.css
mail/themes/gnomestripe/mail/primaryToolbar.css
mail/themes/gnomestripe/mail/userIcon.png
mail/themes/pinstripe/jar.mn
mail/themes/pinstripe/mail/chat.css
mail/themes/pinstripe/mail/icons/chat-toolbar-small.png
mail/themes/pinstripe/mail/icons/chat-toolbar.png
mail/themes/pinstripe/mail/icons/mail-toolbar-small.png
mail/themes/pinstripe/mail/icons/mail-toolbar.png
mail/themes/pinstripe/mail/icons/status-small.png
mail/themes/pinstripe/mail/icons/status.png
mail/themes/pinstripe/mail/preferences/mail-options.png
mail/themes/pinstripe/mail/preferences/preferences.css
mail/themes/pinstripe/mail/primaryToolbar.css
mail/themes/pinstripe/mail/userIcon.png
mail/themes/qute/jar.mn
mail/themes/qute/mail/chat-aero.css
mail/themes/qute/mail/chat.css
mail/themes/qute/mail/icons/chat-toolbar-aero.png
mail/themes/qute/mail/icons/chat-toolbar-small.png
mail/themes/qute/mail/icons/chat-toolbar.png
mail/themes/qute/mail/icons/mail-toolbar-aero.png
mail/themes/qute/mail/icons/mail-toolbar-small.png
mail/themes/qute/mail/icons/mail-toolbar.png
mail/themes/qute/mail/icons/status-aero.png
mail/themes/qute/mail/icons/status-small-aero.png
mail/themes/qute/mail/icons/status-small.png
mail/themes/qute/mail/icons/status.png
mail/themes/qute/mail/preferences/chat-aero.png
mail/themes/qute/mail/preferences/chat.png
mail/themes/qute/mail/preferences/preferences.css
mail/themes/qute/mail/primaryToolbar-aero.css
mail/themes/qute/mail/primaryToolbar.css
mail/themes/qute/mail/userIcon.png
mailnews/base/prefs/content/AccountManager.js
mailnews/base/prefs/content/AccountManager.xul
mailnews/base/util/errUtils.js
mailnews/extensions/mdn/src/mdn-service.js
mailnews/extensions/smime/src/smime-service.js
new file mode 100644
--- /dev/null
+++ b/chat/Makefile.in
@@ -0,0 +1,68 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is
+#   Florian QUEZE <florian@instantbird.org>
+# Portions created by the Initial Developer are Copyright (C) 2008
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either of the GNU General Public License Version 2 or later (the "GPL"),
+# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH		= ..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+PROTOCOLS = \
+		facebook \
+		gtalk \
+		irc \
+		twitter \
+		xmpp \
+		$(NULL)
+
+ifdef MOZ_DEBUG
+PROTOCOLS += jsTest
+endif
+
+PREF_JS_EXPORTS = $(srcdir)/chat-prefs.js
+
+PARALLEL_DIRS	= \
+		components/public \
+		components/src \
+		modules \
+		content \
+		themes \
+		locales \
+		$(foreach proto,$(PROTOCOLS),protocols/$(proto)) \
+		$(NULL)
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/chat/chat-prefs.js
@@ -0,0 +1,93 @@
+// What to do when starting up
+//  0 = do not connect / show the account manager
+//  1 = connect automatically
+//  Other values will be added later, for example to start minimized
+pref("messenger.startup.action", 1);
+
+pref("messenger.accounts", "");
+
+// Should the accounts service stored in the password manager the
+// passwords that are currently stored in the preferences?
+pref("messenger.accounts.convertOldPasswords", false);
+
+// The intervals in seconds between automatic reconnection attempts.
+// The last value will be reused for the rest of the reconnection attempts.
+// A value of 0 means that there will be no more reconnection attempts.
+pref("messenger.accounts.reconnectTimer", "1,5,30,60,90,300,600,1200,3600");
+
+// List of tags ids whose contacts should be shown in the special
+// "Other contacts" group.
+pref("messenger.buddies.hiddenTags", "");
+
+//  1 accepts invitations automatically,
+//  0 ignores the invitations,
+// -1 rejects the invitations.
+pref("messenger.conversations.autoAcceptChatInvitations", 1);
+
+// Indicates whether the core should always close conversations closed
+// by the UI or if they can be put on hold instead.
+pref("messenger.conversations.alwaysClose", false);
+
+pref("messenger.conversations.selections.magicCopyEnabled", true);
+pref("messenger.conversations.selections.ellipsis", "chrome://chat/locale/conversations.properties");
+pref("messenger.conversations.selections.systemMessagesTemplate", "chrome://chat/locale/conversations.properties");
+pref("messenger.conversations.selections.contentMessagesTemplate", "chrome://chat/locale/conversations.properties");
+pref("messenger.conversations.selections.actionMessagesTemplate", "chrome://chat/locale/conversations.properties");
+
+pref("messenger.conversations.textbox.autoResize", true);
+pref("messenger.conversations.textbox.defaultMaxLines", 5);
+
+pref("messenger.conversations.sendFormat", true);
+
+// this preference changes how we filter incoming messages
+// 0 = no formattings
+// 1 = basic formattings (bold, italic, underlined)
+// 2 = permissive mode (colors, font face, font size, ...)
+pref("messenger.options.filterMode", 2);
+
+// use "none" to disable
+pref("messenger.options.emoticonsTheme", "default");
+pref("messenger.options.messagesStyle.theme", "bubbles");
+pref("messenger.options.messagesStyle.variant", "default");
+pref("messenger.options.messagesStyle.showHeader", false);
+pref("messenger.options.messagesStyle.combineConsecutive", true);
+// if the time interval in seconds between two messages is longer than
+// this value, the messages will not be combined
+pref("messenger.options.messagesStyle.combineConsecutiveInterval", 300); // 5 minutes
+
+pref("messenger.status.reportIdle", true);
+pref("messenger.status.timeBeforeIdle", 300); // 5 minutes
+pref("messenger.status.awayWhenIdle", true);
+pref("messenger.status.defaultIdleAwayMessage", "chrome://chat/locale/status.properties");
+pref("messenger.status.userIconFileName", "");
+pref("messenger.status.userDisplayName", "");
+
+// Default message used when quitting IRC. This is overridable per account.
+pref("chat.irc.defaultQuitMessage", "");
+
+// loglevel is the minimum severity level that a libpurple message
+// must have to be reported in the Error Console.
+//
+// The possible values are:
+//   0  Show all libpurple messages (PURPLE_DEBUG_ALL)
+//   1  Very verbose (PURPLE_DEBUG_MISC)
+//   2  Verbose (PURPLE_DEBUG_INFO)
+//   3  Show warnings (PURPLE_DEBUG_WARNING)
+//   4  Show errors (PURPLE_DEBUG_ERROR)
+//   5  Show only fatal errors (PURPLE_DEBUG_FATAL)
+
+// Setting the loglevel to a value smaller than 2 will cause messages
+// with an INFO or MISC severity to be displayed as warnings so that
+// their file URL is clickable
+#ifndef DEBUG
+// By default, show only warning and errors
+pref("purple.debug.loglevel", 3);
+#else
+// On debug builds, show warning, errors and debug information.
+pref("purple.debug.loglevel", 2);
+#endif
+
+pref("purple.logging.format", "json");
+pref("purple.logging.log_chats", true);
+pref("purple.logging.log_ims", true);
+pref("purple.logging.log_system", true);
new file mode 100644
--- /dev/null
+++ b/chat/components/public/Makefile.in
@@ -0,0 +1,65 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is
+# Netscape Communications Corporation.
+# Portions created by the Initial Developer are Copyright (C) 1998
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either of the GNU General Public License Version 2 or later (the "GPL"),
+# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH		= ../../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MODULE		= chat
+
+XPIDLSRCS	= \
+		imIAccount.idl \
+		imIAccountsService.idl \
+		imICommandsService.idl \
+		imIContactsService.idl \
+		imIConversationsService.idl \
+		imICoreService.idl \
+		imILogger.idl \
+		imIStatusInfo.idl \
+		imITagsService.idl \
+		imIUserStatusInfo.idl \
+		prplIConversation.idl \
+		prplIMessage.idl \
+		prplIPref.idl \
+		prplIProtocol.idl \
+		prplIRequest.idl \
+		prplITooltipInfo.idl \
+		$(NULL)
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/chat/components/public/imIAccount.idl
@@ -0,0 +1,295 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2007.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsISupports.idl"
+#include "prplIConversation.idl"
+#include "imIUserStatusInfo.idl"
+
+interface imITag;
+interface imIBuddy;
+interface imIAccountBuddy;
+interface imIAccount;
+interface prplIProtocol;
+interface purpleIProxyInfo;
+
+/*
+ * Used to join chat rooms.
+ */
+
+[ptr] native GHashTablePtr(GHashTable);
+
+[scriptable, uuid(ea7ab156-d25e-46cc-93ef-29f77b3c0795)]
+interface prplIChatRoomFieldValues: nsISupports {
+  AUTF8String getValue(in AUTF8String aIdentifier);
+  void setValue(in AUTF8String aIdentifier, in AUTF8String aValue);
+
+  [noscript] readonly attribute GHashTablePtr hashTable;
+};
+
+[scriptable, uuid(19dff981-b125-4a70-bc1a-efc783d07137)]
+interface prplIChatRoomField: nsISupports {
+  readonly attribute AUTF8String label;
+  readonly attribute AUTF8String identifier;
+  readonly attribute boolean required;
+
+  const short TYPE_TEXT = 0;
+  const short TYPE_PASSWORD = 1;
+  const short TYPE_INT = 2;
+
+  readonly attribute short type;
+  readonly attribute long min;
+  readonly attribute long max;
+};
+
+
+/*
+ * This interface should be implemented by the protocol plugin.
+ */
+[scriptable, uuid(fb1b29cb-63ba-4335-9f44-63aea3f616a3)]
+interface prplIAccount: nsISupports {
+  readonly attribute imIAccount imAccount;
+
+  // observe should only be called by the imIAccount
+  // implementation to report user status changes that affect this account.
+  void observe(in nsISupports aObj, in string aEvent,
+               [optional] in wstring aData);
+
+  /* Uninitialize the prplIAccount instance. This is typically done
+     automatically at shutdown (by the core service) or as part of
+     the 'remove' method. */
+  void unInit();
+
+  void connect();
+  void disconnect();
+
+  prplIConversation createConversation(in AUTF8String aName);
+
+  // Used when the user wants to add a buddy to the buddy list
+  void addBuddy(in imITag aTag, in AUTF8String aName);
+
+  // Used while loading the buddy list at startup.
+  imIAccountBuddy loadBuddy(in imIBuddy aBuddy, in imITag aTag);
+
+  /* Request more info on a buddy (typically a chat buddy).
+   * The result (if any) will be provided by a user-info-received
+   * notification dispatched through the observer service:
+   *  - aSubject will be an nsISimpleEnumerator of prplITooltipInfo.
+   *  - aData will be aBuddyName.
+   */
+  void requestBuddyInfo(in AUTF8String aBuddyName);
+
+  readonly attribute boolean canJoinChat;
+  nsISimpleEnumerator getChatRoomFields();
+  prplIChatRoomFieldValues getChatRoomDefaultFieldValues([optional] in AUTF8String aDefaultChatName);
+  /*
+   * Create a new chat conversation if it doesn't already exist.
+   */
+  void joinChat(in prplIChatRoomFieldValues aComponents);
+
+  readonly attribute AUTF8String normalizedName;
+
+  attribute purpleIProxyInfo proxyInfo;
+
+  // protocol specific options: those functions set the protocol
+  // specific options for the PurpleAccount
+  void setBool(in string aName, in boolean aVal);
+  void setInt(in string aName, in long aVal);
+  void setString(in string aName, in AUTF8String aVal);
+
+  /* When a connection error occurred, this value indicates the type of error */
+  readonly attribute short connectionErrorReason;
+
+  /* Possible connection error reasons:
+     ERROR_NETWORK_ERROR and ERROR_ENCRYPTION_ERROR are not fatal and
+     should enable the automatic reconnection feature. */
+  const short NO_ERROR = -1;
+  const short ERROR_NETWORK_ERROR = 0;
+  const short ERROR_INVALID_USERNAME = 1;
+  const short ERROR_AUTHENTICATION_FAILED = 2;
+  const short ERROR_AUTHENTICATION_IMPOSSIBLE = 3;
+  const short ERROR_NO_SSL_SUPPORT = 4;
+  const short ERROR_ENCRYPTION_ERROR = 5;
+  const short ERROR_NAME_IN_USE = 6;
+  const short ERROR_INVALID_SETTINGS = 7;
+  const short ERROR_CERT_NOT_PROVIDED = 8;
+  const short ERROR_CERT_UNTRUSTED = 9;
+  const short ERROR_CERT_EXPIRED = 10;
+  const short ERROR_CERT_NOT_ACTIVATED = 11;
+  const short ERROR_CERT_HOSTNAME_MISMATCH = 12;
+  const short ERROR_CERT_FINGERPRINT_MISMATCH = 13;
+  const short ERROR_CERT_SELF_SIGNED = 14;
+  const short ERROR_CERT_OTHER_ERROR = 15;
+  const short ERROR_OTHER_ERROR = 16;
+
+  /* From PurpleConnectionFlags */
+
+  //   PURPLE_CONNECTION_HTML
+  //    Connection sends/receives in 'HTML'.
+  readonly attribute boolean HTMLEnabled;
+
+  // libpurple expects messages to be HTML escaped even when HTML
+  // isn't enabled. Our js-prpls most likely don't want that behavior.
+  readonly attribute boolean HTMLEscapePlainText;
+
+  //   PURPLE_CONNECTION_NO_BGCOLOR
+  //    Connection does not send/receive background colors.
+  readonly attribute boolean noBackgroundColors;
+
+  //   PURPLE_CONNECTION_AUTO_RESP
+  //    Send auto responses when away.
+  readonly attribute boolean autoResponses;
+
+  //   PURPLE_CONNECTION_FORMATTING_WBFO
+  //    The text buffer must be formatted as a whole.
+  readonly attribute boolean singleFormatting;
+
+  //   PURPLE_CONNECTION_NO_NEWLINES
+  //    No new lines are allowed in outgoing messages.
+  readonly attribute boolean noNewlines;
+
+  //   PURPLE_CONNECTION_NO_FONTSIZE
+  //    Connection does not send/receive font sizes.
+  readonly attribute boolean noFontSizes;
+
+  //   PURPLE_CONNECTION_NO_URLDESC
+  //    Connection does not support descriptions with links.
+  readonly attribute boolean noUrlDesc;
+
+  //   PURPLE_CONNECTION_NO_IMAGES
+  //    Connection does not support sending of images.
+  readonly attribute boolean noImages;
+
+  // This is currently used only by Twitter.
+  readonly attribute long maxMessageLength;
+};
+
+/* This interface should be implemented by the im core. It inherits
+from prplIAccount and in most cases will forward the calls for the
+inherited members to a prplIAccount account instance implemented by
+the protocol plugin. */
+[scriptable, uuid(20a85b44-e220-4f23-85bf-f8523d1a2b08)]
+interface imIAccount: prplIAccount {
+  /* Check if autologin is enabled for this account, connect it now. */
+  void checkAutoLogin();
+
+  /* Cancel the timer that automatically reconnects the account if it was
+     disconnected because of a non fatal error. */
+  void cancelReconnection();
+
+  readonly attribute AUTF8String name;
+  readonly attribute AUTF8String id;
+  readonly attribute unsigned long numericId;
+  readonly attribute prplIProtocol protocol;
+  readonly attribute prplIAccount prplAccount;
+
+  // Save account specific preferences to disk.
+  void save();
+
+  attribute boolean autoLogin;
+
+  /* This is the value when the preference firstConnectionState is not set.
+     It indicates that the account has already been successfully connected at
+     least once with the current parameters. */
+  const short FIRST_CONNECTION_OK = 0;
+  /* Set when the account has never had a successful connection
+     with the current parameters */
+  const short FIRST_CONNECTION_UNKNOWN = 1;
+  /* Set when the account is trying to connect for the first time
+     with the current parameters (removed after a successsful connection) */
+  const short FIRST_CONNECTION_PENDING = 2;
+  /* Set at startup when the previous state was pending */
+  const short FIRST_CONNECTION_CRASHED = 4;
+
+  attribute short firstConnectionState;
+
+  // Passwords are stored in the toolkit Password Manager.
+  attribute AUTF8String password;
+
+  attribute AUTF8String alias;
+
+  /* While an account is connecting, this attribute contains a message
+     indicating the current step of the connection */
+  readonly attribute AUTF8String connectionStateMsg;
+
+  /* Number of the reconnection attempt
+   *  0 means that no automatic reconnection currently pending
+   *  n means the nth reconnection attempt is pending
+   */
+  readonly attribute unsigned short reconnectAttempt;
+
+  /* Time stamp of the next reconnection attempt */
+  readonly attribute long long timeOfNextReconnect;
+
+  /* Time stamp of the last connection (value not reliable if not connected) */
+  readonly attribute long long timeOfLastConnect;
+
+  /* Additional possible connection error reasons:
+   * (Use a big enough number that it can't conflict with error
+   *  codes used in prplIAccount).
+   */
+  const short ERROR_UNKNOWN_PRPL = 42;
+  const short ERROR_CRASHED = 43;
+  const short ERROR_MISSING_PASSWORD = 44;
+
+  /* A message describing the connection error */
+  readonly attribute AUTF8String connectionErrorMessage;
+
+  /* Info about the connection state and flags */
+  const short STATE_DISCONNECTED = 0;
+  const short STATE_CONNECTED = 1;
+  const short STATE_CONNECTING = 2;
+  const short STATE_DISCONNECTING = 3;
+
+  readonly attribute short connectionState;
+
+  /* The following 4 properties use the above connectionState value. */
+  readonly attribute boolean disconnected;
+  readonly attribute boolean connected;
+  readonly attribute boolean connecting;
+  readonly attribute boolean disconnecting;
+
+  /* The imIUserStatusInfo instance this account should observe for
+     status changes. When this is null (the default value), the
+     account will observe the global status. */
+  attribute imIUserStatusInfo observedStatusInfo;
+  // Same as above, but never null (it fallbacks to the global status info).
+  attribute imIUserStatusInfo statusInfo;
+
+  // imIAccount also implements an observe method but this
+  // observe should only be called by the prplIAccount
+  // implementations to report connection status changes.
+};
new file mode 100644
--- /dev/null
+++ b/chat/components/public/imIAccountsService.idl
@@ -0,0 +1,97 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2007.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsISupports.idl"
+#include "nsISimpleEnumerator.idl"
+#include "imIAccount.idl"
+
+[scriptable, uuid(b3b6459a-5c26-47b8-8e9c-ba838b6f632a)]
+interface imIAccountsService: nsISupports {
+  void initAccounts();
+  void unInitAccounts();
+
+  /* This attribute is set to AUTOLOGIN_ENABLED by default. It can be set to
+     any other value before the initialization of this service to prevent
+     accounts with autoLogin enabled from being connected when libpurple is
+     initialized.
+     Any value other than the ones listed below will disable autoLogin and
+     display a generic message in the Account Manager. */
+  attribute short autoLoginStatus;
+
+  const short AUTOLOGIN_ENABLED = 0;
+  const short AUTOLOGIN_USER_DISABLED = 1;
+  const short AUTOLOGIN_SAFE_MODE = 2;
+  const short AUTOLOGIN_CRASH = 3;
+  const short AUTOLOGIN_START_OFFLINE = 4;
+
+  /* The method should be used to connect all accounts with autoLogin enabled.
+     Some use cases:
+       - if the autologin was disabled at startup
+       - after a loss of internet connectivity that disconnected all accounts.
+  */
+  void processAutoLogin();
+
+  imIAccount getAccountById(in AUTF8String aAccountId);
+
+  /* will throw NS_ERROR_FAILURE if not found */
+  imIAccount getAccountByNumericId(in unsigned long aAccountId);
+
+  nsISimpleEnumerator getAccounts();
+
+  /* will fire the event account-added */
+  imIAccount createAccount(in AUTF8String aName, in AUTF8String aPrpl);
+
+  /* will fire the event account-removed */
+  void deleteAccount(in AUTF8String aAccountId);
+};
+
+/*
+ account related notifications sent to nsIObserverService:
+   - account-added: a new account has been created
+   - account-removed: the account has been deleted
+   - account-connecting: the account is being connected
+   - account-connected: the account is now connected
+   - account-connect-error: the account is disconnect with an error.
+     (before account-disconnecting)
+   - account-disconnecting: the account is being disconnected
+   - account-disconnected: the account is now disconnected
+   - account-updated: when some settings have changed
+   - account-list-updated: when the list of account is reordered.
+   These events can be watched using an nsIObserver.
+   The associated imIAccount will be given as a parameter
+   (except for account-list-updated).
+*/
new file mode 100644
--- /dev/null
+++ b/chat/components/public/imICommandsService.idl
@@ -0,0 +1,110 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2011.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsISupports.idl"
+interface prplIConversation;
+
+[scriptable, uuid(dae17fae-0d04-4d89-a817-637ef433383f)]
+interface imICommand: nsISupports {
+  readonly attribute AUTF8String name;
+
+  // Help message displayed when the user types /help <name>.
+  // Format: <command name> <parameters>: <help message>
+  // Example: "help &lt;name&gt;: show the help message for the &lt;name&gt;
+  //           command, or the list of possible commands when used without
+  //           parameter."
+  readonly attribute AUTF8String helpString;
+
+  const short CONTEXT_IM   = 1;
+  const short CONTEXT_CHAT = 2;
+  const short CONTEXT_ALL  = CONTEXT_IM | CONTEXT_CHAT;
+  readonly attribute long usageContext;
+
+  const short PRIORITY_LOW     = -1000;
+  const short PRIORITY_DEFAULT = 0;
+  const short PRIORITY_PRPL    = 1000;
+  const short PRIORITY_HIGH    = 4000;
+  // Any integer value is usable as a priority.
+  //    0 is the default priority.
+  //  < 0 is lower priority.
+  //  > 0 is higher priority.
+  // Commands registered by protocol plugins will usually use PRIORITY_PRPL.
+  readonly attribute long priority;
+
+  // Will return true if the command handled the message (it should not be sent).
+  // The leading slash, the command name and the following space are not included
+  // in the aMessage parameter.
+  boolean run(in AUTF8String aMessage,
+              [optional] in prplIConversation aConversation);
+};
+
+[scriptable, uuid(467709a0-0bed-4f44-9bdc-13f78b9eaeba)]
+interface imICommandsService: nsISupports {
+  void initCommands();
+  void unInitCommands();
+
+  // Commands registered without a protocol id will work for all protocols.
+  // Registering several commands of the same name with the same
+  // protocol id or no protocol id will replace the former command
+  // with the latter.
+  void registerCommand(in imICommand aCommand,
+                       [optional] in AUTF8String aPrplId);
+
+  // aPrplId should be the same as what was used for the command registration.
+  void unregisterCommand(in AUTF8String aCommandName,
+                         [optional] in AUTF8String aPrplId);
+
+  void listCommandsForConversation(
+    [optional] in prplIConversation aConversation,
+    [optional] out unsigned long commandCount,
+    [retval, array, size_is(commandCount)] out imICommand commands);
+
+  void listCommandsForProtocol(in AUTF8String aPrplId,
+    [optional] out unsigned long commandCount,
+    [retval, array, size_is(commandCount)] out imICommand commands);
+
+  // Will return true if a command handled the message (it should not be sent).
+  // The aConversation parameters is required to execute protocol specific
+  // commands. Application global commands will work without it.
+  boolean executeCommand(in AUTF8String aMessage,
+                         [optional] in prplIConversation aConversation);
+};
+
+%{ C++
+#define IM_COMMANDS_SERVICE_CONTRACTID \
+  "@mozilla.org/chat/commands-service;1"
+%}
new file mode 100644
--- /dev/null
+++ b/chat/components/public/imIContactsService.idl
@@ -0,0 +1,269 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2010.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "imIStatusInfo.idl"
+#include "imITagsService.idl"
+#include "nsISupports.idl"
+#include "nsIObserver.idl"
+#include "nsISimpleEnumerator.idl"
+
+interface imIContact;
+interface imIBuddy;
+interface imIAccountBuddy;
+interface imIAccount;
+interface prplIProtocol;
+
+[scriptable, uuid(f1619b49-310b-47aa-ab1c-238aba084c62)]
+interface imIContactsService: nsISupports {
+  void initContacts();
+  void unInitContacts();
+
+  imIContact getContactById(in long aId);
+  imIBuddy getBuddyById(in long aId);
+  imIBuddy getBuddyByNameAndProtocol(in AUTF8String aNormalizedName,
+                                     in prplIProtocol aPrpl);
+
+  // These 3 functions are called by the protocol plugins when
+  // synchronizing the buddy list with the server stored list,
+  // or after user operations have been performed.
+  void accountBuddyAdded(in imIAccountBuddy aAccountBuddy);
+  void accountBuddyRemoved(in imIAccountBuddy aAccountBuddy);
+  void accountBuddyMoved(in imIAccountBuddy aAccountBuddy,
+                         in imITag aOldTag, in imITag aNewTag);
+
+  // These methods are called by the imIAccountsService implementation
+  // to keep the accounts table in sync with accounts stored in the
+  // preferences.
+
+  // Called when an account is created or loaded to store the new
+  // account or ensure it doesn't conflict with an existing account
+  // (to detect database corruption).
+  // Will throw if a stored account has the id aId but a different
+  // username or prplId.
+  void storeAccount(in unsigned long aId, in AUTF8String aUserName,
+                    in AUTF8String aPrplId);
+  // Check if an account id already exists in the database.
+  boolean accountIdExists(in unsigned long aId);
+  // Called when deleting an account to remove it from blist.sqlite.
+  void forgetAccount(in unsigned long aId);
+};
+
+[scriptable, uuid(f585b0df-f6ad-40d5-9de4-c58b14af13e4)]
+interface imIContact: imIStatusInfo {
+  // The id will be positive if the contact is real (stored in the
+  // SQLite database) and negative if the instance is a dummy contact
+  // holding only a single buddy without aliases or additional tags.
+  readonly attribute long id;
+  attribute AUTF8String alias;
+
+  void getTags([optional] out unsigned long tagCount,
+               [retval, array, size_is(tagCount)] out imITag tags);
+
+  // Will do nothing if the contact already has aTag.
+  void addTag(in imITag aTag);
+  // Will throw if the contact doesn't have aTag or doesn't have any other tag.
+  void removeTag(in imITag aTag);
+
+  readonly attribute imIBuddy preferredBuddy;
+  void getBuddies([optional] out unsigned long buddyCount,
+                  [retval, array, size_is(buddyCount)] out imIBuddy buddies);
+
+  // Move all the buddies of aContact into the current contact,
+  // and copy all its tags.
+  void mergeContact(in imIContact aContact);
+
+  // Change the position of aBuddy in the current contact.
+  // The new position is the current position of aBeforeBuddy if it is
+  // specified, or at the end otherwise.
+  void moveBuddyBefore(in imIBuddy aBuddy, [optional] in imIBuddy aBeforeBuddy);
+
+  // Remove aBuddy from its current contact and append it to the list
+  // of buddies of the current contact.
+  // aBuddy should not already be attached to the current contact.
+  void adoptBuddy(in imIBuddy aBuddy);
+
+  // Returns a new contact that contains only aBuddy, and has the same
+  // list of tags.
+  // Will throw if aBuddy is not a buddy of the contact.
+  imIContact detachBuddy(in imIBuddy aBuddy);
+
+  // remove the contact from the buddy list. Will also remove the
+  // associated buddies.
+  void remove();
+
+  void addObserver(in nsIObserver aObserver);
+  void removeObserver(in nsIObserver aObserver);
+  /* Observers will be notified of changes related to the contact.
+   *  aSubject will point to the imIContact object
+   *  (with some exceptions for contact-moved-* notifications).
+   *
+   *  Fired notifications:
+   *   contact-availability-changed
+   *     when either statusType or availabilityDetails has changed.
+   *   contact-signed-on
+   *   contact-signed-off
+   *   contact-status-changed
+   *     when either statusType or statusText has changed.
+   *   contact-display-name-changed
+   *     when the alias (or serverAlias of the most available buddy if
+   *     no alias is set) has changed.
+   *     The old display name is provided in aData.
+   *   contact-preferred-buddy-changed
+   *     The buddy that would be favored to start a conversation has changed.
+   *   contact-moved, contact-moved-in, contact-moved-out
+   *     contact-moved     is notified through the observer service
+   *     contact-moved-in  is notified to
+   *      - the contact observers (aSubject is the new tag)
+   *      - the new tag           (aSubject is the contact instance)
+   *     contact-moved-out is notified to
+   *      - the contact observers (aSubject is the old tag)
+   *      - the old tag           (aSubject is the contact instance)
+   *   contact-no-longer-dummy
+   *     When a real contact is created to replace a dummy contact.
+   *     The old (negative) id will be given in aData.
+   *     See also the comment above the 'id' attribute.
+   *   contact-icon-changed
+   *
+   * Observers will also receive all the (forwarded) notifications
+   * from the linked buddies (imIBuddy instances) and their account
+   * buddies (imIAccountBuddy instances).
+   */
+
+  // Exposed for add-on authors. All internal calls will come from the
+  // imIContact implementation itself so it wasn't required to expose this.
+  // This can be used to dispatch custom notifications to the
+  // observers of the contact and its tags.
+  // The notification will also be forwarded to the observer service.
+  void notifyObservers(in nsISupports aObj, in string aEvent,
+                       [optional] in wstring aData);
+};
+
+
+[scriptable, uuid(fea582a0-3839-4d80-bcab-0ff82ae8f97f)]
+interface imIBuddy: imIStatusInfo {
+  readonly attribute long id;
+  readonly attribute prplIProtocol protocol;
+  readonly attribute AUTF8String userName; // may be formatted
+  readonly attribute AUTF8String normalizedName; // normalized userName
+  // The optional server alias is in displayName (inherited from imIStatusInfo)
+  // displayName = serverAlias || userName.
+
+  readonly attribute imIContact contact;
+  readonly attribute imIAccountBuddy preferredAccountBuddy;
+  void getAccountBuddies([optional] out unsigned long accountBuddyCount,
+                         [retval, array, size_is(accountBuddyCount)] out imIAccountBuddy accountBuddies);
+
+  // remove the buddy from the buddy list. If the contact becomes empty, it will be removed too.
+  void remove();
+
+  void addObserver(in nsIObserver aObserver);
+  void removeObserver(in nsIObserver aObserver);
+  /* Observers will be notified of changes related to the buddy.
+   *  aSubject will point to the imIBuddy object.
+   *  Fired notifications:
+   *   buddy-availability-changed
+   *     when either statusType or availabilityDetails has changed.
+   *   buddy-signed-on
+   *   buddy-signed-off
+   *   buddy-status-changed
+   *     when either statusType or statusText has changed.
+   *   buddy-display-name-changed
+   *     when the serverAlias has changed.
+   *     The old display name is provided in aData.
+   *   buddy-preferred-account-changed
+   *     The account that would be favored to start a conversation has changed.
+   *   buddy-icon-changed
+   *
+   * Observers will also receive all the (forwarded) notifications
+   * from the linked account buddies (imIAccountBuddy instances).
+   */
+
+  // Exposed for add-on authors. All internal calls will come from the
+  // imIBuddy implementation itself so it wasn't required to expose this.
+  // This can be used to dispatch custom notifications to the
+  // observers of the buddy, its contact and its tags.
+  // The contact will forward the notifications to the observer service.
+  void notifyObservers(in nsISupports aObj, in string aEvent,
+                       [optional] in wstring aData);
+
+  // observe should only be called by the imIAccountBuddy
+  // implementations to report changes.
+  void observe(in nsISupports aObj, in string aEvent,
+               [optional] in wstring aData);
+};
+
+/* imIAccountBuddy implementations can send notifications to their buddy:
+ *
+ * For all of them, aSubject points to the imIAccountBuddy object.
+ *
+ * Supported notifications:
+ *  account-buddy-availability-changed
+ *    when either statusType or availabilityDetails has changed.
+ *  account-buddy-signed-on
+ *  account-buddy-signed-off
+ *  account-buddy-status-changed
+ *    when either statusType or statusText has changed.
+ *  account-buddy-display-name-changed
+ *    when the serverAlias has changed.
+ *    The old display name is provided in aData.
+ *  account-buddy-icon-changed
+ *
+ * All notifications (even unsupported ones) will be forwarded to the contact,
+ * its tags and nsObserverService.
+ */
+[scriptable, uuid(114d24ff-56a1-4fd6-9822-4992efb6e036)]
+interface imIAccountBuddy: imIStatusInfo {
+  // The setter is for internal use only. buddy will be set by the
+  // Contacts service when accountBuddyAdded is called on this
+  // instance of imIAccountBuddy.
+           attribute imIBuddy buddy;
+  readonly attribute imIAccount account;
+  // Setting the tag will move the buddy to a different group on the
+  // server-stored buddy list.
+           attribute imITag tag;
+  readonly attribute AUTF8String userName;
+  readonly attribute AUTF8String normalizedName;
+           attribute AUTF8String serverAlias;
+
+  // remove the buddy from the buddy list of this account.
+  void remove();
+
+  // Called by the contacts service during its uninitialization to
+  // notify that all references kept to imIBuddy or imIAccount
+  // instances should be released now.
+  void unInit();
+};
new file mode 100644
--- /dev/null
+++ b/chat/components/public/imIConversationsService.idl
@@ -0,0 +1,98 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsISupports.idl"
+#include "prplIConversation.idl"
+#include "imIContactsService.idl"
+
+interface prplIMessage;
+
+[scriptable, uuid(a09faf46-bb9d-402f-b460-89f8d7827ff1)]
+interface imIConversation: prplIConversation {
+  // Will be null for MUCs and IMs from people not in the contacts list.
+  readonly attribute imIContact contact;
+
+  // Write a system message into the conversation.
+  // Note: this will not be logged.
+  void systemMessage(in AUTF8String aMessage, [optional] in boolean aIsError);
+
+  attribute prplIConversation target;
+
+  // Number of unread messages (all messages, including system
+  // messages are counted).
+  readonly attribute unsigned long unreadMessageCount;
+  // Number of unread incoming messages targeted at the user (= IMs or
+  // message containing the user's nick in MUCs).
+  readonly attribute unsigned long unreadTargetedMessageCount;
+  // Number of unread incoming messages (both targeted and untargeted
+  // messages are counted).
+  readonly attribute unsigned long unreadIncomingMessageCount;
+  // Reset all unread message counts.
+  void markAsRead();
+
+  // Call this to give the core an opportunity to close an inactive
+  // conversation.  If the conversation is a left MUC or an IM
+  // conversation without unread message, the implementation will call
+  // close().
+  // The returned value indicates if the conversation was closed.
+  boolean checkClose();
+
+  // Get an array of all messages of the conversation.
+  void getMessages([optional] out unsigned long messageCount,
+                   [retval, array, size_is(messageCount)] out prplIMessage messages);
+};
+
+[scriptable, uuid(984e182c-d395-4fba-ba6e-cc80c71f57bf)]
+interface imIConversationsService: nsISupports {
+  void initConversations();
+  void unInitConversations();
+
+  // register a conversation. This will create a unique id for the
+  // conversation and set it.
+  void addConversation(in prplIConversation aConversation);
+  void removeConversation(in prplIConversation aConversation);
+
+  void getUIConversations([optional] out unsigned long conversationCount,
+                          [retval, array, size_is(conversationCount)] out imIConversation conversations);
+  imIConversation getUIConversation(in prplIConversation aConversation);
+  imIConversation getUIConversationByContactId(in long aId);
+
+  nsISimpleEnumerator getConversations();
+  prplIConversation getConversationById(in unsigned long aId);
+  prplIConversation getConversationByNameAndAccount(in AUTF8String aName,
+                                                      in imIAccount aAccount,
+                                                      in boolean aIsChat);
+};
new file mode 100644
--- /dev/null
+++ b/chat/components/public/imICoreService.idl
@@ -0,0 +1,54 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2007.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsISupports.idl"
+#include "imIUserStatusInfo.idl"
+#include "nsISimpleEnumerator.idl"
+#include "prplIProtocol.idl"
+
+[scriptable, uuid(205d4b2b-1ccf-4879-9ef1-f08942566151)]
+interface imICoreService: nsISupports {
+  void init();
+  void quit(); // will emit a prpl-quit notification.
+
+  // returns an enumerator on a pplIProtocol array
+  nsISimpleEnumerator getProtocols();
+
+  prplIProtocol getProtocolById(in AUTF8String aProtocolId);
+
+  readonly attribute imIUserStatusInfo globalUserStatus;
+};
new file mode 100644
--- /dev/null
+++ b/chat/components/public/imILogger.idl
@@ -0,0 +1,87 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2010.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsISupports.idl"
+#include "nsISimpleEnumerator.idl"
+#include "nsIFile.idl"
+
+interface imIAccount;
+interface imIAccountBuddy;
+interface imIBuddy;
+interface imIContact;
+interface prplIConversation;
+interface prplIMessage;
+
+[scriptable, uuid(5bc06f3b-33a1-412b-a4d8-4fc7ba4c962b)]
+interface imILogConversation: nsISupports {
+  readonly attribute AUTF8String title;
+  readonly attribute AUTF8String name;
+
+  // Simplified account implementation:
+  //  - alias will always be empty
+  //  - name (always the normalizedName)
+  //  - statusInfo will return Services.core.globalUserStatus
+  //  - protocol will only contain a "name" attribute, with the prpl's normalized name.
+  // Other methods/attributes aren't implemented.
+  readonly attribute imIAccount account;
+
+  readonly attribute boolean isChat; // always false (compatibility with prplIConversation).
+  readonly attribute imIAccountBuddy buddy; // always null (compatibility with prplIConvIM).
+
+  void getMessages([optional] out unsigned long messageCount,
+                   [retval, array, size_is(messageCount)] out prplIMessage messages);
+};
+
+[scriptable, uuid(164ff6c3-ca64-4880-b8f3-67eb1817955f)]
+interface imILog: nsISupports {
+  readonly attribute AUTF8String path;
+  readonly attribute PRTime time;
+  readonly attribute AUTF8String format;
+  // Will return null if the log format isn't json.
+  imILogConversation getConversation();
+};
+
+[scriptable, uuid(ab38c01c-2245-4279-9437-1d6bcc69d556)]
+interface imILogger: nsISupports {
+  imILog getLogFromFile(in nsIFile aFile);
+  nsISimpleEnumerator getLogsForAccountBuddy(in imIAccountBuddy aAccountBuddy);
+  nsISimpleEnumerator getLogsForBuddy(in imIBuddy aBuddy);
+  nsISimpleEnumerator getLogsForContact(in imIContact aContact);
+  nsISimpleEnumerator getLogsForConversation(in prplIConversation aConversation);
+  nsISimpleEnumerator getSystemLogsForAccount(in imIAccount aAccount);
+  nsISimpleEnumerator getSimilarLogs(in imILog aLog);
+};
new file mode 100644
--- /dev/null
+++ b/chat/components/public/imIStatusInfo.idl
@@ -0,0 +1,87 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2010.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsISupports.idl"
+#include "nsISimpleEnumerator.idl"
+#include "prplIConversation.idl"
+
+[scriptable, uuid(f13dc4fc-5334-45cb-aa58-a92851955e55)]
+interface imIStatusInfo: nsISupports {
+  // Name suitable for display in the UI. Can either be the username,
+  // the server side alias, or the user set local alias of the contact.
+  readonly attribute AUTF8String displayName;
+  readonly attribute AUTF8String buddyIconFilename;
+
+  const short STATUS_UNKNOWN = 0;
+  const short STATUS_OFFLINE = 1;
+  const short STATUS_INVISIBLE = 2;
+  const short STATUS_MOBILE = 3;
+  const short STATUS_IDLE = 4;
+  const short STATUS_AWAY = 5;
+  const short STATUS_UNAVAILABLE = 6;
+  const short STATUS_AVAILABLE = 7;
+
+  // numerical value used to compare the availability of two buddies
+  // based on their current status.
+  //  Use it only for immediate comparisons, do not store the value,
+  //  it can change between versions for a same status of the buddy.
+  readonly attribute long statusType;
+
+  readonly attribute boolean online;    // (statusType > STATUS_OFFLINE)
+  readonly attribute boolean available; // (statusType == STATUS_AVAILABLE)
+  readonly attribute boolean idle;      // (statusType == STATUS_IDLE)
+  readonly attribute boolean mobile;    // (statusType == STATUS_MOBILE)
+
+  readonly attribute AUTF8String statusText;
+
+  // Gives more detail to compare the availability of two buddies with the same
+  // status type.
+  //  Example: 2 buddies may have been idle for various amounts of times.
+  readonly attribute long availabilityDetails;
+
+  // True if the buddy is online or if the account supports sending
+  // offline messages to the buddy.
+  readonly attribute boolean canSendMessage;
+
+  // enumerator of purpleTooltipInfo components
+  nsISimpleEnumerator getTooltipInfo();
+
+  // Will select the buddy automatically based on availability, and
+  // the account (if needed) based on the account order in the account
+  // manager.
+  prplIConversation createConversation();
+};
new file mode 100644
--- /dev/null
+++ b/chat/components/public/imITagsService.idl
@@ -0,0 +1,89 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2010.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsISupports.idl"
+#include "nsIObserver.idl"
+
+interface imIContact;
+
+[scriptable, uuid(c211e5e2-f0a4-4a86-9e4c-3f6b905628a5)]
+interface imITag: nsISupports {
+  readonly attribute long id;
+           attribute AUTF8String name;
+
+  // Get an array of all the contacts associated with this tag.
+  //  Contacts can either "have the tag" (added by user action) or
+  //  have inherited the tag because it was the server side group for
+  //  one of the AccountBuddy of the contact.
+  void getContacts([optional] out unsigned long contactCount,
+                   [retval, array, size_is(contactCount)] out imIContact contacts);
+
+  void addObserver(in nsIObserver aObserver);
+  void removeObserver(in nsIObserver aObserver);
+  /* Observers will be notified of changes related to the contacts
+   * that have the tag: contact-*, buddy-*, account-buddy-*
+   * notifications forwarded respectively from the imIContact,
+   * imIBuddy and imIAccountBuddy instances.
+   */
+
+  // Exposed for add-on authors. All internal calls will come from the
+  // imITag implementation itself so it wasn't required to expose this.
+  // This can be used to dispatch custom notifications to the
+  // observers of the tag.
+  void notifyObservers(in nsISupports aObj, in string aEvent,
+                       [optional] in wstring aData);
+};
+
+[scriptable, uuid(f799a9c2-23f2-4fd1-96fb-515bad238f8c)]
+interface imITagsService: nsISupports {
+  // Create a new tags or return the existing one if it already exists
+  imITag createTag(in AUTF8String aName);
+  // Get an existing tag by (numeric) id. Returns null if not found.
+  imITag getTagById(in long aId);
+  // Get an existing tag by name (will do an SQL query). Returns null
+  // if not found.
+  imITag getTagByName(in AUTF8String aName);
+  // Get an array of all existing tags.
+  void getTags([optional] out unsigned long tagCount,
+               [retval, array, size_is(tagCount)] out imITag tags);
+
+  boolean isTagHidden(in imITag aTag);
+  void hideTag(in imITag aTag);
+  void showTag(in imITag aTag);
+
+  readonly attribute imITag otherContactsTag;
+};
new file mode 100644
--- /dev/null
+++ b/chat/components/public/imIUserStatusInfo.idl
@@ -0,0 +1,76 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2010.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsISupports.idl"
+#include "nsIObserver.idl"
+
+//forward declarations
+interface nsILocalFile;
+interface nsIFileURL;
+
+[scriptable, uuid(817918fa-1f4b-4254-9cdb-f906da91c45d)]
+interface imIUserStatusInfo: nsISupports {
+
+  readonly attribute AUTF8String statusText;
+
+  // See imIStatusInfo for the values.
+  readonly attribute short statusType;
+
+  // Only works with STATUS_OFFLINE, STATUS_UNAVAILABLE, STATUS_AWAY,
+  // STATUS_AVAILABLE and STATUS_INVISIBLE.
+  // - When called with the status type STATUS_UNSET, only the status
+  //   message will be changed.
+  // - When called with STATUS_OFFLINE, the aMessage parameter is ignored.
+  void setStatus(in short aStatus, in AUTF8String aMessage);
+
+  /* Will fire an user-icon-changed notificaton. */
+  void setUserIcon(in nsILocalFile aIconFile);
+
+  nsIFileURL getUserIcon();
+
+  /* The setter will fire an user-display-name-changed notificaton. */
+  attribute AUTF8String displayName;
+
+  void addObserver(in nsIObserver aObserver);
+  void removeObserver(in nsIObserver aObserver);
+  /* Observers will receive the following notifications:
+   *   status-changed (when either the status type or text has changed)
+   *   user-icon-changed
+   *   user-display-name-changed
+   *   idle-time-changed
+   */
+};
new file mode 100644
--- /dev/null
+++ b/chat/components/public/prplIConversation.idl
@@ -0,0 +1,179 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2007.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+
+#include "nsISupports.idl"
+#include "nsISimpleEnumerator.idl"
+#include "nsIObserver.idl"
+
+interface imIAccountBuddy;
+interface imIAccount;
+interface nsIURI;
+interface nsIDOMDocument;
+
+/*
+ * This is the XPCOM purple conversation component, a proxy for PurpleConversation.
+ */
+
+[scriptable, uuid(74fca337-b8e7-4e5d-81cd-8aba3dba9208)]
+interface prplIConversation: nsISupports {
+
+  /* Indicate if this conversation implements prplIConvIM or prplIConvChat */
+  readonly attribute boolean isChat;
+
+  /* The account used for this conversation */
+  readonly attribute imIAccount account;
+
+  /* The name of the conversation, typically in English */
+  readonly attribute AUTF8String name;
+
+  /* The normalized name of the conversation (suitable for a log file name) */
+  readonly attribute AUTF8String normalizedName;
+
+  /* The title of the conversation, typically localized */
+  readonly attribute AUTF8String title;
+
+  /* Unique identifier of the conversation */
+  /* Setable only once by purpleCoreService while calling addConversation. */
+           attribute unsigned long id;
+
+  /* Send a message in the conversation */
+  void sendMsg(in AUTF8String aMsg);
+
+  /* Send information about the current typing state to the server.
+     aLength should contain the length of the content currently in the text field.
+     A length value of 0 will make the method send a NOT_TYPING message. */
+  void sendTyping(in unsigned long aLength);
+
+  /* Un-initialize the conversation. Will be called by
+     purpleCoreService::RemoveConversation when the conversation is
+     closed or by purpleCoreService::Quit while exiting. */
+  void unInit();
+
+  /* When the conversation is closed from the UI. */
+  void close();
+
+  /* Method to add or remove an observer */
+  void addObserver(in nsIObserver aObserver);
+  void removeObserver(in nsIObserver aObserver);
+
+  /* Observers will be all receive new-text notifications.
+     aSubject will contain the message (prplIMessage) */
+};
+
+[scriptable, uuid(0c072a80-103a-4992-b249-8e442b5f0d46)]
+interface prplIConvIM: prplIConversation {
+
+  /* The buddy at the remote end of the conversation */
+  readonly attribute imIAccountBuddy buddy;
+
+  /* The remote buddy is not currently typing */
+  const short NOT_TYPING = 0;
+
+  /* The remote buddy is currently typing */
+  const short TYPING = 1;
+
+  /* The remote buddy started typing, but has stopped typing */
+  const short TYPED = 2;
+
+  /* The typing state of the remote buddy.
+     The value is NOT_TYPING, TYPING or TYPED. */
+  readonly attribute short typingState;
+};
+
+[scriptable, uuid(c7f11466-f479-4f12-a581-b99713b8ecc0)]
+interface prplIConvChat: prplIConversation {
+
+  /* Get an nsISimpleEnumerator of prplIConvChatBuddy objects:
+     The list of people participating in this chat */
+  nsISimpleEnumerator getParticipants();
+
+  /* The normalized chat buddy name will be suitable for starting a
+     private conversation or calling requestBuddyInfo. */
+  AUTF8String getNormalizedChatBuddyName(in AUTF8String aChatBuddyName);
+
+  /* The topic of this chat room */
+           attribute AUTF8String topic;
+
+  /* The name/nick of the person who set the topic */
+  readonly attribute AUTF8String topicSetter;
+
+  /* Whether the protocol plugin can set a topic. Doesn't check that
+     the user has the necessary rights in the current conversation. */
+  readonly attribute boolean topicSettable;
+
+  /* The nick seen by other people in the room */
+  readonly attribute AUTF8String nick;
+
+  /* This is true when we left the chat but kept the conversation open */
+  readonly attribute boolean left;
+
+  /* Observers will receive chat-buddy-add, chat-buddy-update,
+     chat-buddy-remove and chat-update-topic notifications.
+
+     aSubject will be of type:
+       nsISimpleEnumerator of prplIConvChatBuddy for chat-buddy-add,
+       nsISimpleEnumerator of nsISupportsString for chat-buddy-remove,
+       prplIConvChatBuddy for chat-buddy-update,
+       null for chat-update-topic.
+
+     aData will contain the old nick for chat-buddy-update if the name
+     has changed.
+   */
+};
+
+/* This represents a participant in a chat room */
+[scriptable, uuid(33f6ef81-1d23-484e-b4e0-14fffa0c4392)]
+interface prplIConvChatBuddy: nsISupports {
+
+  /* The name of the buddy */
+  readonly attribute AUTF8String name;
+
+  /* The alias (FIXME: can this be non-null if buddy is null?) */
+  readonly attribute AUTF8String alias;
+
+  /* Indicates if this chat buddy corresponds to a buddy in our buddy list */
+  readonly attribute boolean buddy;
+
+  /* PurpleConvChatBuddyFlags flags; (ops, voice etc.) */
+  readonly attribute boolean noFlags;
+  readonly attribute boolean voiced;
+  readonly attribute boolean halfOp;
+  readonly attribute boolean op;
+  readonly attribute boolean founder;
+  readonly attribute boolean typing;
+};
new file mode 100644
--- /dev/null
+++ b/chat/components/public/prplIMessage.idl
@@ -0,0 +1,111 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2007.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsISupports.idl"
+#include "nsIRunnable.idl"
+#include "prplIConversation.idl"
+
+/*
+ * An action that the user may perform in relation to a particular message.
+ */
+[scriptable, uuid(7e470f0e-d948-4d9a-b8dc-4beecf6554b9)]
+interface prplIMessageAction: nsIRunnable
+{
+  /* The protocol plugins need to provide a localized label suitable
+     for being shown in the user interface (for example as a context
+     menu item). */
+  readonly attribute ACString label;
+};
+
+[scriptable, uuid(d9f0ca7f-ee59-4657-a3dd-f458c204ca45)]
+interface prplIMessage: nsISupports {
+  /* The uniqueness of the message id is only guaranteed across
+     messages of a conversation, not across all messages created
+     during the execution of the application. */
+  readonly attribute unsigned long id;
+  readonly attribute AUTF8String who;
+  readonly attribute AUTF8String alias;
+  readonly attribute AUTF8String originalMessage;
+           attribute AUTF8String message;
+  readonly attribute AUTF8String iconURL;
+  readonly attribute PRTime time;
+  readonly attribute prplIConversation conversation;
+
+  /* Holds the sender color for Chats.
+     Empty string by default, it is set by the conversation binding. */
+  attribute AUTF8String color;
+
+  /*  PURPLE_MESSAGE_SEND        = 0x0001, /**< Outgoing message. */
+  readonly attribute boolean outgoing;
+  /*  PURPLE_MESSAGE_RECV        = 0x0002, /**< Incoming message. */
+  readonly attribute boolean incoming;
+  /*  PURPLE_MESSAGE_SYSTEM      = 0x0004, /**< System message.   */
+  readonly attribute boolean system;
+  /*  PURPLE_MESSAGE_AUTO_RESP   = 0x0008, /**< Auto response.    */
+  readonly attribute boolean autoResponse;
+  /*  PURPLE_MESSAGE_ACTIVE_ONLY = 0x0010, /**< Hint to the UI that this
+                                                message should not be
+                                                shown in conversations
+                                                which are only open for
+                                                internal UI purposes
+                                                (e.g. for contact-aware
+                                                 conversions).           */
+  /*  PURPLE_MESSAGE_NICK        = 0x0020, /**< Contains your nick.      */
+  readonly attribute boolean containsNick;
+  /*  PURPLE_MESSAGE_NO_LOG      = 0x0040, /**< Do not log.              */
+  readonly attribute boolean noLog;
+  /*  PURPLE_MESSAGE_ERROR       = 0x0200, /**< Error message.           */
+  readonly attribute boolean error;
+  /*  PURPLE_MESSAGE_DELAYED     = 0x0400, /**< Delayed message.         */
+  readonly attribute boolean delayed;
+  /*  PURPLE_MESSAGE_RAW         = 0x0800, /**< "Raw" message - don't
+                                                apply formatting         */
+  readonly attribute boolean noFormat;
+  /*  PURPLE_MESSAGE_IMAGES      = 0x1000, /**< Message contains images  */
+  readonly attribute boolean containsImages;
+  /*  PURPLE_MESSAGE_NOTIFY      = 0x2000, /**< Message is a notification */
+  readonly attribute boolean notification;
+  /*  PURPLE_MESSAGE_NO_LINKIFY  = 0x4000  /**< Message should not be auto-linkified */
+  readonly attribute boolean noLinkification;
+
+  /* An array of actions the user may perform on this message.
+     The first action will be the 'default' and may be performed
+     automatically when the message is double clicked.
+     'Reply' is usually a good default action. */
+  void getActions([optional] out unsigned long actionCount,
+                  [retval, array, size_is(actionCount)] out prplIMessageAction actions);
+};
new file mode 100644
--- /dev/null
+++ b/chat/components/public/prplIPref.idl
@@ -0,0 +1,69 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2007.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsISupports.idl"
+#include "nsISimpleEnumerator.idl"
+
+/*
+ * This is a proxy for libpurple PurpleAccountOption
+ */
+
+[scriptable, uuid(e781563f-9088-4a96-93e3-4fb6f5ce6a77)]
+interface prplIPref: nsISupports {
+  const short typeBool   = 1;
+  const short typeInt    = 2;
+  const short typeString = 3;
+  const short typeList   = 4;
+
+  readonly attribute AUTF8String name;
+  readonly attribute AUTF8String label;
+  readonly attribute short type;
+  readonly attribute boolean masked;
+
+  boolean     getBool();
+  long        getInt();
+  AUTF8String getString();
+  // enumerator of prplIKeyValuePair
+  nsISimpleEnumerator getList();
+  AUTF8String getListDefault();
+};
+
+[scriptable, uuid(8fc16882-ba8e-432a-999f-0d4dc104234b)]
+interface prplIKeyValuePair: nsISupports {
+  readonly attribute AUTF8String name;
+  readonly attribute AUTF8String value;
+};
new file mode 100644
--- /dev/null
+++ b/chat/components/public/prplIProtocol.idl
@@ -0,0 +1,133 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2007.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsISupports.idl"
+#include "nsISimpleEnumerator.idl"
+#include "imIAccount.idl"
+
+[scriptable, uuid(7d302db0-3813-4c51-8372-c7eb5fc9f3d3)]
+interface prplIProtocol: nsISupports {
+  // aId is the prpl id, this method is used so that classes
+  // implementing several protocol plugins can know which protocol is
+  // desired for this instance.
+  void init(in AUTF8String aId);
+
+  readonly attribute AUTF8String name;
+  readonly attribute AUTF8String id;
+  readonly attribute AUTF8String normalizedName;
+
+  // returns a chrome URI pointing to a folder that contains the files:
+  // icon.png icon32.png and icon48.png
+  readonly attribute AUTF8String iconBaseURI;
+
+  // returns an enumerator of prplIPref
+  nsISimpleEnumerator getOptions();
+
+  // returns an enumerator of prplIUsernameSplit
+  nsISimpleEnumerator getUsernameSplit();
+
+  // descriptive text to put in the username input box when it is empty
+  readonly attribute AUTF8String usernameEmptyText;
+
+  // Use this function to avoid attempting to create duplicate accounts
+  boolean accountExists(in AUTF8String aName);
+
+  // These should all be stuff that some plugins can do and others can't.
+
+  // OPT_PROTO_UNIQUE_CHATNAME Use a unique name, not an alias, for
+  // chat rooms.
+  //  XMPP lets you choose what name you want for chat. So it
+  //  shouldn't be pulling the alias for when you're in chat; it gets
+  //  annoying.
+  readonly attribute boolean uniqueChatName;
+
+  // OPT_PROTO_CHAT_TOPIC Chat rooms have topics.
+  //  IRC and XMPP support this.
+  readonly attribute boolean chatHasTopic;
+
+  // OPT_PROTO_NO_PASSWORD Don't require passwords for sign-in.
+  //  Zephyr doesn't require passwords, so there's no need for a
+  //  password prompt.
+  readonly attribute boolean noPassword;
+
+  // OPT_PROTO_MAIL_CHECK Notify on new mail.
+  //  MSN and Yahoo notify you when you have new mail.
+  readonly attribute boolean newMailNotification;
+
+  // OPT_PROTO_IM_IMAGE Images in IMs.
+  //  Oscar lets you send images in direct IMs.
+  readonly attribute boolean imagesInIM;
+
+  // OPT_PROTO_PASSWORD_OPTIONAL Allow passwords to be optional.
+  //  Passwords in IRC are optional, and are needed for certain
+  //  functionality.
+  readonly attribute boolean passwordOptional;
+
+  // OPT_PROTO_USE_POINTSIZE Allows font size to be specified in sane
+  // point size.
+  //  Probably just XMPP and Y!M
+  readonly attribute boolean usePointSize;
+
+  // OPT_PROTO_REGISTER_NOSCREENNAME Set the Register button active
+  // when screenname is not given.
+  //  Gadu-Gadu doesn't need a screenname to register new account.
+  readonly attribute boolean registerNoScreenName;
+
+  // OPT_PROTO_SLASH_COMMANDS_NATIVE  Indicates that slash commands
+  // are native to this protocol.
+  // Used as a hint that unknown commands should not be sent as messages.
+  readonly attribute boolean slashCommandsNative;
+
+  // Indicates if the protocol plugin can use a purpleIProxy for the
+  // account, or uses the Mozilla proxy settings for all accounts.
+  readonly attribute boolean usePurpleProxy;
+
+  // Get the protocol specific part of an already initialized
+  // imIAccount instance.
+  prplIAccount getAccount(in imIAccount aImAccount);
+};
+
+[scriptable, uuid(20c4971a-f7c2-4781-8e85-69fee7b83a3d)]
+interface prplIUsernameSplit: nsISupports {
+  readonly attribute AUTF8String label;
+  readonly attribute AUTF8String defaultValue;
+  readonly attribute char separator;
+
+  /* reverse is PR_TRUE if the separator should be found starting at
+     the end of the string, PR_FALSE otherwise. */
+  readonly attribute boolean reverse;
+};
new file mode 100644
--- /dev/null
+++ b/chat/components/public/prplIRequest.idl
@@ -0,0 +1,69 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2010.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsISupports.idl"
+
+interface imIAccount;
+interface nsIDOMWindow;
+interface nsIWebProgress;
+
+/* This interface is for use in the browser-request notification, to
+   let protocol plugins open a browser window. This is an unfortunate
+   necessity for protocols that require an OAuth authentication. */
+[scriptable, uuid(b89dbb38-0de4-11e0-b3d0-0002e304243c)]
+interface prplIRequestBrowser: nsISupports {
+  readonly attribute AUTF8String promptText;
+  readonly attribute imIAccount account;
+  readonly attribute AUTF8String url;
+  void cancelled();
+  void loaded(in nsIDOMWindow aWindow,
+              in nsIWebProgress aWebProgress);
+};
+
+/* This interface is used for buddy authorization requests, when the
+   user needs to confirm if a remote contact should be allowed to see
+   his presence information.  It is implemented by the aSubject
+   parameter of the buddy-authorization-request and
+   buddy-authorization-request-canceled notifications.
+*/
+[scriptable, uuid(a55c1e24-17cc-4ddc-8c64-3bc315a3c3b1)]
+interface prplIBuddyRequest: nsISupports {
+  readonly attribute imIAccount account;
+  readonly attribute AUTF8String userName;
+  void grant();
+  void deny();
+};
new file mode 100644
--- /dev/null
+++ b/chat/components/public/prplITooltipInfo.idl
@@ -0,0 +1,54 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2008.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsISupports.idl"
+
+/*
+ * This interface provides access to the content of a
+ * PurpleNotifyUserInfoEntry structure.
+ */
+
+[scriptable, uuid(dd535741-4b04-49ca-8df6-08f8577fe52b)]
+interface prplITooltipInfo: nsISupports {
+  const short pair          = 0;
+  const short sectionBreak  = 1;
+  const short sectionHeader = 2;
+
+  readonly attribute short type;
+  readonly attribute AUTF8String label;
+  readonly attribute AUTF8String value;
+};
new file mode 100644
--- /dev/null
+++ b/chat/components/src/Makefile.in
@@ -0,0 +1,54 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is
+# Netscape Communications Corporation.
+# Portions created by the Initial Developer are Copyright (C) 1998
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either of the GNU General Public License Version 2 or later (the "GPL"),
+# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH		= ../../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+EXTRA_COMPONENTS = \
+		imAccounts.js imAccounts.manifest \
+		imCommands.js imCommands.manifest \
+		imContacts.js imContacts.manifest \
+		imConversations.js imConversations.manifest \
+		imCore.js imCore.manifest \
+		logger.js logger.manifest \
+		smileProtocolHandler.js smileProtocolHandler.manifest \
+		$(NULL)
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/chat/components/src/imAccounts.js
@@ -0,0 +1,982 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2011.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Romain Bezut <romain@bezut.info>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+Cu.import("resource:///modules/imXPCOMUtils.jsm");
+Cu.import("resource:///modules/imServices.jsm");
+
+const kPrefAutologinPending = "messenger.accounts.autoLoginPending";
+const kPrefMessengerAccounts = "messenger.accounts";
+const kPrefAccountPrefix = "messenger.account.";
+const kAccountKeyPrefix = "account";
+const kAccountOptionPrefPrefix = "options.";
+const kPrefAccountName = "name";
+const kPrefAccountPrpl = "prpl";
+const kPrefAccountAutoLogin = "autoLogin";
+const kPrefAccountAutoJoin = "autoJoin";
+const kPrefAccountAlias = "alias";
+const kPrefAccountFirstConnectionState = "firstConnectionState";
+
+const kPrefConvertOldPasswords = "messenger.accounts.convertOldPasswords";
+const kPrefAccountPassword = "password";
+
+XPCOMUtils.defineLazyGetter(this, "LoginManager", function()
+  Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager)
+);
+
+XPCOMUtils.defineLazyGetter(this, "_", function()
+  l10nHelper("chrome://chat/locale/accounts.properties")
+);
+
+var gUserCanceledMasterPasswordPrompt = false;
+var gConvertingOldPasswords = false;
+
+var SavePrefTimer = {
+  saveNow: function() {
+    if (this._timer) {
+      clearTimeout(this._timer);
+      this._timer = null;
+    }
+    Services.prefs.savePrefFile(null);
+  },
+  _timer: null,
+  unInitTimer: function() {
+    if (this._timer)
+      this.saveNow();
+  },
+  initTimer: function() {
+    if (!this._timer)
+      this._timer = setTimeout(this.saveNow.bind(this), 5000);
+  }
+};
+
+var AutoLoginCounter = {
+  _count: 0,
+  startAutoLogin: function() {
+    ++this._count;
+    if (this._count != 1)
+      return;
+    Services.prefs.setIntPref(kPrefAutologinPending, Date.now() / 1000);
+    SavePrefTimer.saveNow();
+  },
+  finishedAutoLogin: function() {
+    --this._count;
+    if (this._count != 0)
+      return;
+    Services.prefs.deleteBranch(kPrefAutologinPending);
+    SavePrefTimer.initTimer();
+  }
+};
+
+function UnknownProtocol(aPrplId)
+{
+  this.id = aPrplId;
+}
+UnknownProtocol.prototype = {
+  __proto__: ClassInfo("prplIProtocol", "Unknown protocol"),
+  get name() "",
+  get normalizedName() this.name,
+  get iconBaseURI() "chrome://chat/skin/prpl-unknown/",
+  getOptions: function() EmptyEnumerator,
+  getUsernameSplit: function() EmptyEnumerator,
+  get usernameEmptyText() "",
+
+  getAccount: function(aKey, aName) { throw Cr.NS_ERROR_NOT_IMPLEMENTED; },
+  accountExists: function() { throw Cr.NS_ERROR_NOT_IMPLEMENTED; },
+
+  // false seems an acceptable default for all options
+  // (they should never be called anyway).
+  get uniqueChatName() false,
+  get chatHasTopic() false,
+  get noPassword() false,
+  get newMailNotification() false,
+  get imagesInIM() false,
+  get passwordOptional() true,
+  get usePointSize() true,
+  get registerNoScreenName() false,
+  get slashCommandsNative() false,
+  get usePurpleProxy() false
+};
+
+// aName and aPrplId are provided as parameter only if this is a new
+// account that doesn't exist in the preferences. In this case, these
+// 2 values should be stored.
+function imAccount(aKey, aName, aPrplId)
+{
+  if (aKey.indexOf(kAccountKeyPrefix) != 0)
+    throw Cr.NS_ERROR_INVALID_ARG;
+
+  this.id = aKey;
+  this.numericId = parseInt(aKey.substr(kAccountKeyPrefix.length));
+  this.prefBranch = Services.prefs.getBranch(kPrefAccountPrefix + aKey + ".");
+
+  if (aName) {
+    this.name = aName;
+    let str = Cc["@mozilla.org/supports-string;1"]
+              .createInstance(Ci.nsISupportsString);
+    str.data = aName;
+    this.prefBranch.setComplexValue(kPrefAccountName, Ci.nsISupportsString,
+                                    str);
+
+    this.firstConnectionState = Ci.imIAccount.FIRST_CONNECTION_UNKNOWN;
+  }
+  else {
+    this.name = this.prefBranch.getComplexValue(kPrefAccountName,
+                                                Ci.nsISupportsString).data;
+  }
+
+  let prplId = aPrplId;
+  if (prplId)
+    this.prefBranch.setCharPref(kPrefAccountPrpl, prplId);
+  else
+    prplId = this.prefBranch.getCharPref(kPrefAccountPrpl);
+
+  // Get the protocol plugin, or fallback to an UnknownProtocol instance.
+  this.protocol = Services.core.getProtocolById(prplId);
+  if (!this.protocol) {
+    this.protocol = new UnknownProtocol(prplId);
+    this._connectionErrorReason = Ci.imIAccount.ERROR_UNKNOWN_PRPL;
+    return;
+  }
+
+  // Ensure the account is correctly stored in blist.sqlite.
+  Services.contacts.storeAccount(this.numericId, this.name, prplId);
+
+  // Get the prplIAccount from the protocol plugin.
+  this.prplAccount = this.protocol.getAccount(this);
+
+  // Send status change notifications to the account.
+  this.observedStatusInfo = null; // (To execute the setter).
+
+  // If we have never finished the first connection attempt for this account,
+  // mark the account as having caused a crash.
+  if (this.firstConnectionState == Ci.imIAccount.FIRST_CONNECTION_PENDING)
+    this.firstConnectionState = Ci.imIAccount.FIRST_CONNECTION_CRASHED;
+
+  // Try to convert old passwords stored in the preferences.
+  // Don't try too hard if the user has canceled a master password prompt:
+  // we don't want to display several of theses prompts at startup.
+  if (gConvertingOldPasswords && !this.protocol.noPassword) {
+    try {
+      let password = this.prefBranch.getComplexValue(kPrefAccountPassword,
+                                                     Ci.nsISupportsString).data;
+      if (password && !this.password)
+        this.password = password;
+    } catch (e) { /* No password saved in the prefs for this account. */ }
+  }
+
+  // Check for errors that should prevent connection attempts.
+  if (this._passwordRequired && !this.password)
+    this._connectionErrorReason = Ci.imIAccount.ERROR_MISSING_PASSWORD;
+  else if (this.firstConnectionState == Ci.imIAccount.FIRST_CONNECTION_CRASHED)
+    this._connectionErrorReason = Ci.imIAccount.ERROR_CRASHED;
+}
+
+imAccount.prototype = {
+  __proto__: ClassInfo(["imIAccount", "prplIAccount"], "im account object"),
+
+  name: "",
+  id: "",
+  numericId: 0,
+  protocol: null,
+  prplAccount: null,
+  connectionState: Ci.imIAccount.STATE_DISCONNECTED,
+  connectionStateMsg: "",
+  connectionErrorMessage: "",
+  _connectionErrorReason: Ci.prplIAccount.NO_ERROR,
+  get connectionErrorReason() {
+    if (this._connectionErrorReason != Ci.prplIAccount.NO_ERROR &&
+        (this._connectionErrorReason != Ci.imIAccount.ERROR_MISSING_PASSWORD ||
+         !this._password))
+      return this._connectionErrorReason;
+    else
+      return this.prplAccount.connectionErrorReason;
+  },
+
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic == "account-connect-progress")
+      this.connectionStateMsg = aData;
+    else if (aTopic == "account-connecting") {
+      if (this.prplAccount.connectionErrorReason != Ci.prplIAccount.NO_ERROR) {
+        delete this.connectionErrorMessage;
+        if (this.timeOfNextReconnect - Date.now() > 1000) {
+          // This is a manual reconnection, reset the auto-reconnect stuff
+          this.timeOfLastConnect = 0;
+          this.cancelReconnection();
+        }
+      }
+      if (this.firstConnectionState != Ci.imIAccount.FIRST_CONNECTION_OK)
+        this.firstConnectionState = Ci.imIAccount.FIRST_CONNECTION_PENDING;
+      this.connectionState = Ci.imIAccount.STATE_CONNECTING;
+    }
+    else if (aTopic == "account-connected") {
+      this.connectionState = Ci.imIAccount.STATE_CONNECTED;
+      this._finishedAutoLogin();
+      this.timeOfLastConnect = Date.now();
+      if (this.firstConnectionState != Ci.imIAccount.FIRST_CONNECTION_OK)
+        this.firstConnectionState = Ci.imIAccount.FIRST_CONNECTION_OK;
+      delete this.connectionStateMsg;
+
+      if (this.canJoinChat &&
+          this.prefBranch.prefHasUserValue(kPrefAccountAutoJoin)) {
+        let autojoin = this.prefBranch.getCharPref(kPrefAccountAutoJoin);
+        if (autojoin) {
+          for each (let room in autojoin.split(","))
+            this.joinChat(this.getChatRoomDefaultFieldValues(room));
+        }
+      }
+    }
+    else if (aTopic == "account-disconnecting") {
+      this.connectionState = Ci.imIAccount.STATE_DISCONNECTING;
+      this.connectionErrorMessage = aData;
+      delete this.connectionStateMsg;
+      this._finishedAutoLogin();
+
+      let firstConnectionState = this.firstConnectionState;
+      if (firstConnectionState != Ci.imIAccount.FIRST_CONNECTION_OK &&
+          firstConnectionState != Ci.imIAccount.FIRST_CONNECTION_CRASHED)
+        this.firstConnectionState = Ci.imIAccount.FIRST_CONNECTION_UNKNOWN;
+
+      let connectionErrorReason = this.prplAccount.connectionErrorReason;
+      if (connectionErrorReason != Ci.prplIAccount.NO_ERROR) {
+        if (connectionErrorReason == Ci.prplIAccount.ERROR_NETWORK_ERROR ||
+            connectionErrorReason == Ci.prplIAccount.ERROR_ENCRYPTION_ERROR)
+          this._startReconnectTimer();
+        this._sendNotification("account-connect-error");
+      }
+    }
+    else if (aTopic == "account-disconnected")
+      this.connectionState = Ci.imIAccount.STATE_DISCONNECTED;
+    else
+      throw Cr.NS_ERROR_UNEXPECTED;
+    this._sendNotification(aTopic, aData);
+  },
+
+  _observedStatusInfo: null,
+  get observedStatusInfo() this._observedStatusInfo,
+  _statusObserver: null,
+  set observedStatusInfo(aUserStatusInfo) {
+    if (!this.prplAccount)
+      return;
+    if (this._statusObserver)
+      this.statusInfo.removeObserver(this._statusObserver);
+    this._observedStatusInfo = aUserStatusInfo;
+    if (this._statusObserver)
+      this.statusInfo.addObserver(this._statusObserver);
+  },
+  get statusInfo() this._observedStatusInfo || Services.core.globalUserStatus,
+
+  reconnectAttempt: 0,
+  timeOfLastConnect: 0,
+  timeOfNextReconnect: 0,
+  _reconnectTimer: null,
+  _startReconnectTimer: function() {
+    /* If the last successful connection is older than 10 seconds, reset the
+     number of reconnection attemps. */
+    const kTimeBeforeSuccessfulConnection = 10;
+    if (this.timeOfLastConnect &&
+        this.timeOfLastConnect + kTimeBeforeSuccessfulConnection * 1000 < Date.now()) {
+      delete this.reconnectAttempt;
+      delete this.timeOfLastConnect;
+    }
+
+    let timers =
+      Services.prefs.getCharPref("messenger.accounts.reconnectTimer").split(",");
+    let delay = timers[Math.min(this.reconnectAttempt, timers.length - 1)];
+    let msDelay = parseInt(delay) * 1000;
+    ++this.reconnectAttempt;
+    this.timeOfNextReconnect = Date.now() + msDelay;
+    this._reconnectTimer = setTimeout(this.connect.bind(this), msDelay);
+  },
+
+  _sendNotification: function(aTopic, aData) {
+    Services.obs.notifyObservers(this, aTopic, aData);
+  },
+
+  get firstConnectionState() {
+    try {
+      return this.prefBranch.getIntPref(kPrefAccountFirstConnectionState);
+    } catch (e) {
+      return Ci.imIAccount.FIRST_CONNECTION_OK;
+    }
+  },
+  set firstConnectionState(aState) {
+    if (aState == Ci.imIAccount.FIRST_CONNECTION_OK)
+      this.prefBranch.deleteBranch(kPrefAccountFirstConnectionState);
+    else {
+      this.prefBranch.setIntPref(kPrefAccountFirstConnectionState, aState);
+      // We want to save this pref immediately when trying to connect.
+      if (aState == Ci.imIAccount.FIRST_CONNECTION_PENDING)
+        SavePrefTimer.saveNow();
+      else
+        SavePrefTimer.initTimer();
+    }
+  },
+
+  _pendingReconnectForConnectionInfoChange: false,
+  _connectionInfoChanged: function() {
+    /* This will be the first connection with these parameters */
+    this.firstConnectionState = Ci.imIAccount.FIRST_CONNECTION_UNKNOWN;
+
+    if (this._pendingReconnectForConnectionInfoChange)
+      return;
+
+    this._pendingReconnectForConnectionInfoChange = true;
+    executeSoon(function () {
+      delete this._pendingReconnectForConnectionInfoChange;
+      // If the connection parameters have changed while we were
+      // trying to connect, cancel the ongoing connection attempt and
+      // try again with the new parameters.
+      if (this.connecting) {
+        this.disconnect();
+        this.connect();
+        return;
+      }
+      // If the account was disconnected because of a non-fatal
+      // connection error, retry now that we have new parameters.
+      let errorReason = this.connectionErrorReason;
+      if (this.disconnected &&
+          errorReason != Ci.prplIAccount.NO_ERROR &&
+          errorReason != Ci.imIAccount.ERROR_MISSING_PASSWORD &&
+          errorReason != Ci.imIAccount.ERROR_CRASHED &&
+          errorReason != Ci.imIAccount.ERROR_UNKNOWN_PRPL) {
+        this.connect();
+      }
+    }.bind(this));
+  },
+
+  get normalizedName() this._ensurePrplAccount.normalizedName,
+
+  _sendUpdateNotification: function() {
+    this._sendNotification("account-updated");
+  },
+
+  set alias(val) {
+    if (val) {
+      let str = Cc["@mozilla.org/supports-string;1"]
+                .createInstance(Ci.nsISupportsString);
+      str.data = val;
+      this.prefBranch.setComplexValue(kPrefAccountAlias, Ci.nsISupportsString,
+                                      str);
+    }
+    else
+      this.prefBranch.deleteBranch(kPrefAccountAlias);
+    this._sendUpdateNotification();
+  },
+  get alias() {
+    try {
+      return this.prefBranch.getComplexValue(kPrefAccountAlias,
+                                             Ci.nsISupportsString).data;
+    } catch (e) {
+      return "";
+    }
+  },
+
+  _password: "",
+  get password() {
+    if (this._password)
+      return this._password;
+
+    // Avoid prompting the user for the master password more than once at startup.
+    if (gUserCanceledMasterPasswordPrompt)
+      return "";
+
+    let passwordURI = "im://" + this.protocol.id;
+    let logins;
+    try {
+      logins = LoginManager.findLogins({}, passwordURI, null, passwordURI);
+    } catch (e) {
+      this._handleMasterPasswordException(e);
+      return "";
+    }
+    let normalizedName = this.normalizedName;
+    for each (let login in logins) {
+      if (login.username == normalizedName) {
+        this._password = login.password;
+        if (this._connectionErrorReason == Ci.imIAccount.ERROR_MISSING_PASSWORD) {
+          // We have found a password for an account marked as missing password,
+          // re-check all others accounts missing a password. But first,
+          // remove the error on our own account to avoid re-checking it.
+          delete this._connectionErrorReason;
+          gAccountsService._checkIfPasswordStillMissing();
+        }
+        return this._password;
+      }
+    }
+    return "";
+  },
+  _checkIfPasswordStillMissing: function() {
+    if (this._connectionErrorReason != Ci.imIAccount.ERROR_MISSING_PASSWORD ||
+        !this.password)
+      return;
+
+    delete this._connectionErrorReason;
+    this._sendUpdateNotification();
+  },
+  get _passwordRequired()
+    !this.protocol.noPassword && !this.protocol.passwordOptional,
+  set password(aPassword) {
+    this._password = aPassword;
+    if (gUserCanceledMasterPasswordPrompt)
+      return;
+    let newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"]
+                   .createInstance(Ci.nsILoginInfo);
+    let passwordURI = "im://" + this.protocol.id;
+    newLogin.init(passwordURI, null, passwordURI, this.normalizedName,
+                  aPassword, "", "");
+    try {
+      let logins = LoginManager.findLogins({}, passwordURI, null, passwordURI);
+      let saved = false;
+      for each (let login in logins) {
+        if (newLogin.matches(login, true)) {
+          if (aPassword)
+            LoginManager.modifyLogin(login, newLogin);
+          else
+            LoginManager.removeLogin(login);
+          saved = true;
+          break;
+        }
+      }
+      if (!saved && aPassword)
+        LoginManager.addLogin(newLogin);
+    } catch (e) {
+      this._handleMasterPasswordException(e);
+    }
+
+    this._connectionInfoChanged();
+    if (aPassword &&
+        this._connectionErrorReason == Ci.imIAccount.ERROR_MISSING_PASSWORD)
+      this._connectionErrorReason = Ci.imIAccount.NO_ERROR;
+    else if (!aPassword && this._passwordRequired)
+      this._connectionErrorReason = Ci.imIAccount.ERROR_MISSING_PASSWORD;
+    this._sendUpdateNotification();
+  },
+  _handleMasterPasswordException: function(aException) {
+    if (aException.result != Components.results.NS_ERROR_ABORT)
+      throw aException;
+
+    gUserCanceledMasterPasswordPrompt = true;
+    executeSoon(function () { gUserCanceledMasterPasswordPrompt = false; });
+  },
+
+  get autoLogin() {
+    let autoLogin = true;
+    try {
+      autoLogin = this.prefBranch.getBoolPref(kPrefAccountAutoLogin);
+    } catch (e) { }
+    return autoLogin;
+  },
+  set autoLogin(val) {
+    this.prefBranch.setBoolPref(kPrefAccountAutoLogin, val);
+    SavePrefTimer.initTimer();
+    this._sendUpdateNotification();
+  },
+  _autoLoginPending: false,
+  checkAutoLogin: function() {
+    // No auto-login if: the account has an error at the imIAccount level
+    // (unknown protocol, missing password, first connection crashed),
+    // the account is already connected or connecting, or autoLogin is off.
+    if (this._connectionErrorReason != Ci.prplIAccount.NO_ERROR ||
+        this.connecting || this.connected || !this.autoLogin)
+      return;
+
+    this._autoLoginPending = true;
+    AutoLoginCounter.startAutoLogin();
+    try {
+      this.connect();
+    } catch (e) {
+      Cu.reportError(e);
+      this._finishedAutoLogin();
+    }
+  },
+  _finishedAutoLogin: function() {
+    if (!this.hasOwnProperty("_autoLoginPending"))
+      return;
+    delete this._autoLoginPending;
+    AutoLoginCounter.finishedAutoLogin();
+  },
+
+  // Delete the account (from the preferences, mozStorage, and call unInit).
+  remove: function() {
+    let login = Cc["@mozilla.org/login-manager/loginInfo;1"]
+                .createInstance(Ci.nsILoginInfo);
+    let passwordURI = "im://" + this.protocol.id;
+    login.init(passwordURI, null, passwordURI, this.normalizedName, "", "", "");
+    let logins = LoginManager.findLogins({}, passwordURI, null, passwordURI);
+    for each (let l in logins) {
+      if (login.matches(l, true)) {
+        LoginManager.removeLogin(l);
+        break;
+      }
+    }
+    this.unInit();
+    Services.contacts.forgetAccount(this.numericId);
+    this.prefBranch.deleteBranch("");
+  },
+  unInit: function() {
+    // remove any pending reconnection timer.
+    this.cancelReconnection();
+
+    // remove any pending autologin preference used for crash detection.
+    this._finishedAutoLogin();
+
+    // If the first connection was pending on quit, we set it back to unknown.
+    if (this.firstConnectionState == Ci.imIAccount.FIRST_CONNECTION_PENDING)
+      this.firstConnectionState = Ci.imIAccount.FIRST_CONNECTION_UNKNOWN;
+
+    // and make sure we cleanup the save pref timer.
+    SavePrefTimer.unInitTimer();
+
+    if (this.prplAccount)
+      this.prplAccount.unInit();
+
+    delete this.protocol;
+    delete this.prplAccount;
+  },
+
+  get _ensurePrplAccount() {
+    if (this.prplAccount)
+      return this.prplAccount;
+    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+  },
+  connect: function() {
+    if (this._passwordRequired) {
+      // If the previous connection attempt failed because we have a wrong password,
+      // clear the passwor cache so that if there's no password in the password
+      // manager the user gets prompted again.
+      if (this.connectionErrorReason == Ci.prplIAccount.ERROR_AUTHENTICATION_FAILED)
+        delete this._password;
+
+      let password = this.password;
+      if (!password) {
+        let prompts = Services.prompt;
+        let shouldSave = {value: false};
+        password = {value: ""};
+        if (!prompts.promptPassword(null, _("passwordPromptTitle", this.name),
+                                    _("passwordPromptText", this.name),
+                                    password, _("passwordPromptSaveCheckbox"),
+                                    shouldSave))
+          return;
+
+        if (shouldSave.value)
+          this.password = password.value;
+        else
+          this._password = password.value;
+      }
+    }
+
+    if (!this._statusObserver) {
+      this._statusObserver = {
+        observe: (function(aSubject, aTopic, aData) {
+          // Disconnect or reconnect the account automatically, otherwise notify
+          // the prplAccount instance.
+          let statusType = aSubject.statusType;
+          if (statusType == Ci.imIStatusInfo.STATUS_OFFLINE &&
+              this.connected)
+            this.prplAccount.disconnect();
+          else if (statusType == Ci.imIStatusInfo.STATUS_OFFLINE &&
+                   this._reconnectTimer)
+            this.cancelReconnection();
+          else if (statusType > Ci.imIStatusInfo.STATUS_OFFLINE &&
+                   this.disconnected)
+            this.prplAccount.connect();
+          else if (this.connected)
+            this.prplAccount.observe(aSubject, aTopic, aData);
+        }).bind(this)
+      };
+
+      this.statusInfo.addObserver(this._statusObserver);
+    }
+
+    this._ensurePrplAccount.connect();
+  },
+  disconnect: function() {
+    if (this._statusObserver) {
+      this.statusInfo.removeObserver(this._statusObserver);
+      delete this._statusObserver;
+    }
+    this._ensurePrplAccount.disconnect();
+  },
+
+  get disconnected() this.connectionState == Ci.imIAccount.STATE_DISCONNECTED,
+  get connected() this.connectionState == Ci.imIAccount.STATE_CONNECTED,
+  get connecting() this.connectionState == Ci.imIAccount.STATE_CONNECTING,
+  get disconnecting() this.connectionState == Ci.imIAccount.STATE_DISCONNECTING,
+
+  cancelReconnection: function() {
+    if (this._reconnectTimer) {
+      clearTimeout(this._reconnectTimer);
+      delete this._reconnectTimer;
+    }
+    delete this.reconnectAttempt;
+    delete this.timeOfNextReconnect;
+  },
+  createConversation: function(aName)
+    this._ensurePrplAccount.createConversation(aName),
+  addBuddy: function(aTag, aName) {
+    this._ensurePrplAccount.addBuddy(aTag, aName);
+  },
+  loadBuddy: function(aBuddy, aTag)
+    this._ensurePrplAccount.loadBuddy(aBuddy, aTag), // FIXME for unknown proto
+  requestBuddyInfo: function(aBuddyName) {
+    this._ensurePrplAccount.requestBuddyInfo(aBuddyName);
+  },
+  getChatRoomFields: function() this._ensurePrplAccount.getChatRoomFields(),
+  getChatRoomDefaultFieldValues: function(aDefaultChatName)
+    this._ensurePrplAccount.getChatRoomDefaultFieldValues(aDefaultChatName),
+  get canJoinChat() this.prplAccount ? this.prplAccount.canJoinChat : false,
+  joinChat: function(aComponents) {
+    this._ensurePrplAccount.joinChat(aComponents);
+  },
+  setBool: function(aName, aVal) {
+    this.prefBranch.setBoolPref(kAccountOptionPrefPrefix + aName, aVal);
+    this._connectionInfoChanged();
+    if (this.prplAccount)
+      this.prplAccount.setBool(aName, aVal);
+    SavePrefTimer.initTimer();
+  },
+  setInt: function(aName, aVal) {
+    this.prefBranch.setIntPref(kAccountOptionPrefPrefix + aName, aVal);
+    this._connectionInfoChanged();
+    if (this.prplAccount)
+      this.prplAccount.setInt(aName, aVal);
+    SavePrefTimer.initTimer();
+  },
+  setString: function(aName, aVal) {
+    let str = Cc["@mozilla.org/supports-string;1"]
+              .createInstance(Ci.nsISupportsString);
+    str.data = aVal;
+    this.prefBranch.setComplexValue(kAccountOptionPrefPrefix + aName,
+                                    Ci.nsISupportsString, str);
+    this._connectionInfoChanged();
+    if (this.prplAccount)
+      this.prplAccount.setString(aName, aVal);
+    SavePrefTimer.initTimer();
+  },
+  save: function() { SavePrefTimer.saveNow(); },
+
+  get HTMLEnabled() this._ensurePrplAccount.HTMLEnabled,
+  get HTMLEscapePlainText() this._ensurePrplAccount.HTMLEscapePlainText,
+  get noBackgroundColors() this._ensurePrplAccount.noBackgroundColors,
+  get autoResponses() this._ensurePrplAccount.autoResponses,
+  get singleFormatting() this._ensurePrplAccount.singleFormatting,
+  get noNewlines() this._ensurePrplAccount.noNewlines,
+  get noFontSizes() this._ensurePrplAccount.noFontSizes,
+  get noUrlDesc() this._ensurePrplAccount.noUrlDesc,
+  get noImages() this._ensurePrplAccount.noImages,
+  get maxMessageLength() this._ensurePrplAccount.maxMessageLength,
+
+  get proxyInfo() this._ensurePrplAccount.proxyInfo,
+  set proxyInfo(val) {
+    this._ensurePrplAccount.proxyInfo = val;
+    this._connectionInfoChanged();
+  }
+};
+
+var gAccountsService = null;
+
+function AccountsService() { }
+AccountsService.prototype = {
+  initAccounts: function() {
+    this._initAutoLoginStatus();
+    this._accounts = [];
+    this._accountsById = {};
+    gAccountsService = this;
+    gConvertingOldPasswords =
+      Services.prefs.getBoolPref(kPrefConvertOldPasswords);
+    let accountList = this._accountList;
+    for each (let account in (accountList ? accountList.split(",") : [])) {
+      try {
+        account.trim();
+        if (!account)
+          throw Cr.NS_ERROR_INVALID_ARG;
+        let newAccount = new imAccount(account);
+        this._accounts.push(newAccount);
+        this._accountsById[newAccount.numericId] = newAccount;
+      } catch (e) {
+        Cu.reportError(e);
+        dump(e + " " + e.toSource() + "\n");
+      }
+    }
+    // If the user has canceled a master password prompt, we haven't
+    // been able to save any password, so the old password conversion
+    // still needs to happen.
+    if (gConvertingOldPasswords && !gUserCanceledMasterPasswordPrompt)
+      Services.prefs.setBoolPref(kPrefConvertOldPasswords, false);
+
+    this._prefObserver = this.observe.bind(this);
+    Services.prefs.addObserver(kPrefMessengerAccounts, this._prefObserver, false);
+  },
+
+  _observingAccountListChange: true,
+  _prefObserver: null,
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic != "nsPref:changed" || aData != kPrefMessengerAccounts ||
+       !this._observingAccountListChange)
+      return;
+
+    this._accounts =
+      this._accountList.split(",").map(String.trim)
+          .filter(function (k) k.indexOf(kAccountKeyPrefix) == 0)
+          .map(function (k) parseInt(k.substr(kAccountKeyPrefix.length)))
+          .map(this.getAccountByNumericId, this)
+          .filter(function (a) a);
+
+    Services.obs.notifyObservers(this, "account-list-updated", null);
+  },
+
+  get _accountList() Services.prefs.getCharPref(kPrefMessengerAccounts),
+  set _accountList(aNewList) {
+    this._observingAccountListChange = false;
+    Services.prefs.setCharPref(kPrefMessengerAccounts, aNewList);
+    delete this._observingAccountListChange;
+  },
+
+  unInitAccounts: function() {
+    for each (let account in this._accounts)
+      account.unInit();
+    gAccountsService = null;
+    delete this._accounts;
+    delete this._accountsById;
+    Services.prefs.removeObserver(kPrefMessengerAccounts, this._prefObserver);
+    delete this._prefObserver;
+  },
+
+  autoLoginStatus: Ci.imIAccountsService.AUTOLOGIN_ENABLED,
+  _initAutoLoginStatus: function() {
+    /* If auto-login is already disabled, do nothing */
+    if (this.autoLoginStatus != Ci.imIAccountsService.AUTOLOGIN_ENABLED)
+      return;
+
+    let prefs = Services.prefs;
+    if (!prefs.getIntPref("messenger.startup.action")) {
+      // the value 0 means that we start without connecting the accounts
+      this.autoLoginStatus = Ci.imIAccountsService.AUTOLOGIN_USER_DISABLED;
+      return;
+    }
+
+    /* Disable auto-login if we are running in safe mode */
+    if (Services.appinfo.inSafeMode) {
+      this.autoLoginStatus = Ci.imIAccountsService.AUTOLOGIN_SAFE_MODE;
+      return;
+    }
+
+    /* If the application was started offline, disable auto-login */
+    if (!Services.io.manageOfflineStatus && Services.io.offline) {
+      this.autoLoginStatus = Ci.imIAccountsService.AUTOLOGIN_START_OFFLINE;
+      return;
+    }
+
+    /* Check if we crashed at the last startup during autologin */
+    let autoLoginPending;
+    if (prefs.getPrefType(kPrefAutologinPending) == prefs.PREF_INVALID ||
+        !(autoLoginPending = prefs.getIntPref(kPrefAutologinPending))) {
+      // if the pref isn't set, then we haven't crashed: keep autologin enabled
+      return;
+    }
+
+    // Last autologin hasn't finished properly.
+    // For now, assume it's because of a crash.
+    this.autoLoginStatus = Ci.imIAccountsService.AUTOLOGIN_CRASH;
+    prefs.deleteBranch(kPrefAutologinPending);
+
+    // If the crash reporter isn't built, we can't know anything more.
+    if (!("nsICrashReporter" in Ci))
+      return;
+
+    try {
+      // Try to get more info with breakpad
+      let lastCrashTime = 0;
+
+      /* Locate the LastCrash file */
+      let lastCrash = Services.dirsvc.get("UAppData", Ci.nsILocalFile);
+      lastCrash.append("Crash Reports");
+      lastCrash.append("LastCrash");
+      if (lastCrash.exists()) {
+        /* Ok, the file exists, now let's try to read it */
+        let is = Cc["@mozilla.org/network/file-input-stream;1"]
+                 .createInstance(Ci.nsIFileInputStream);
+        let sis = Cc["@mozilla.org/scriptableinputstream;1"]
+                  .createInstance(Ci.nsIScriptableInputStream);
+        is.init(lastCrash, -1, 0, 0);
+        sstream.init(sis);
+
+        lastCrashTime = parseInt(sstream.read(lastCrash.fileSize));
+
+        sstream.close();
+        fstream.close();
+      }
+      // The file not existing is totally acceptable, it just means that
+      // either we never crashed or breakpad is not enabled.
+      // In this case, lastCrashTime will keep its 0 initialization value.
+
+      /*dump("autoLoginPending = " + autoLoginPending +
+             ", lastCrash = " + lastCrashTime +
+             ", difference = " + lastCrashTime - autoLoginPending + "\n");*/
+
+      if (lastCrashTime < autoLoginPending) {
+        // the last crash caught by breakpad is older than our last autologin
+        // attempt.
+        // If breakpad is currently enabled, we can be confident that
+        // autologin was interrupted for an exterior reason
+        // (application killed by the user, power outage, ...)
+        try {
+          Services.appinfo.QueryInterface(Ci.nsICrashReporter)
+                  .annotateCrashReport("=", "");
+        } catch (e) {
+          // This should fail with NS_ERROR_INVALID_ARG if breakpad is enabled,
+          // and NS_ERROR_NOT_INITIALIZED if it is not.
+          if (e.result != Cr.NS_ERROR_NOT_INITIALIZED)
+            this.autoLoginStatus = Ci.imIAccountsService.AUTOLOGIN_ENABLED;
+        }
+      }
+    } catch (e) {
+      // if we failed to get the last crash time, then keep the
+      // AUTOLOGIN_CRASH value in mAutoLoginStatus and return.
+      return;
+    }
+  },
+
+  processAutoLogin: function() {
+    if (Services.io.offline)
+      throw Cr.NS_ERROR_FAILURE;
+
+    for each (let account in this._accounts)
+      account.checkAutoLogin();
+
+    // Make sure autologin is now enabled, so that we don't display a
+    // message stating that it is disabled and asking the user if it
+    // should be processed now.
+    this.autoLoginStatus = Ci.imIAccountsService.AUTOLOGIN_ENABLED;
+
+    // Notify observers so that any message stating that autologin is
+    // disabled can be removed
+    Services.obs.notifyObservers(this, "autologin-processed", null);
+  },
+
+  _checkingIfPasswordStillMissing: false,
+  _checkIfPasswordStillMissing: function() {
+    // Avoid recursion.
+    if (this._checkingIfPasswordStillMissing)
+      return;
+
+    this._checkingIfPasswordStillMissing = true;
+    for each (let account in this._accounts)
+      account._checkIfPasswordStillMissing();
+    delete this._checkingIfPasswordStillMissing;
+  },
+
+  getAccountById: function(aAccountId) {
+    if (aAccountId.indexOf(kAccountKeyPrefix) != 0)
+      throw Cr.NS_ERROR_INVALID_ARG;
+
+    let id = parseInt(aAccountId.substr(kAccountKeyPrefix.length));
+    return this.getAccountByNumericId(id);
+  },
+
+  getAccountByNumericId: function(aAccountId) this._accountsById[aAccountId],
+  getAccounts: function() new nsSimpleEnumerator(this._accounts),
+
+  createAccount: function(aName, aPrpl) {
+    // Ensure an account with the same name and protocol doesn't already exist.
+    let prpl = Services.core.getProtocolById(aPrpl);
+    if (!prpl)
+      throw Cr.NS_ERROR_UNEXPECTED;
+    if (prpl.accountExists(aName)) {
+      Cu.reportError("Attempted to create a duplicate account!");
+      throw Cr.NS_ERROR_ALREADY_INITIALIZED;
+    }
+
+    /* First get a unique id for the new account. */
+    let id;
+    for (id = 1; ; ++id) {
+      if (this._accountsById.hasOwnProperty(id))
+        continue;
+
+      /* id isn't used by a known account, double check it isn't
+       already used in the sqlite database. This should never
+       happen, except if we have a corrupted profile. */
+      if (!Services.contacts.accountIdExists(id))
+        break;
+      Services.console.logStringMessage("No account " + id + " but there is some data in the buddy list for an account with this number. Your profile may be corrupted.");
+    }
+
+    /* Actually create the new account. */
+    let key = kAccountKeyPrefix + id;
+    let account = new imAccount(key, aName, aPrpl);
+
+    /* Keep it in the local account lists. */
+    this._accounts.push(account);
+    this._accountsById[id] = account;
+
+    /* Save the account list pref. */
+    let list = this._accountList;
+    this._accountList = list ? list + "," + key : key;
+
+    Services.obs.notifyObservers(account, "account-added", null);
+    return account;
+  },
+
+  deleteAccount: function(aAccountId) {
+    let account = this.getAccountById(aAccountId);
+    if (!account)
+      throw Cr.NS_ERROR_INVALID_ARG;
+
+    let index = this._accounts.indexOf(account);
+    if (index == -1)
+      throw Cr.NS_ERROR_UNEXPECTED;
+
+    let id = account.numericId;
+    account.remove();
+    this._accounts.splice(index, 1);
+    delete this._accountsById[id];
+    Services.obs.notifyObservers(account, "account-removed", null);
+
+    /* Update the account list pref. */
+    let list = this._accountList;
+    this._accountList =
+      list.split(",").filter(function (k) k.trim() != aAccountId).join(",");
+  },
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.imIAccountsService]),
+  classDescription: "Accounts",
+  classID: Components.ID("{a94b5427-cd8d-40cf-b47e-b67671953e70}"),
+  contractID: "@mozilla.org/chat/accounts-service;1"
+};
+
+const NSGetFactory = XPCOMUtils.generateNSGetFactory([AccountsService]);
new file mode 100644
--- /dev/null
+++ b/chat/components/src/imAccounts.manifest
@@ -0,0 +1,2 @@
+component {a94b5427-cd8d-40cf-b47e-b67671953e70} imAccounts.js
+contract @mozilla.org/chat/accounts-service;1 {a94b5427-cd8d-40cf-b47e-b67671953e70}
new file mode 100644
--- /dev/null
+++ b/chat/components/src/imCommands.js
@@ -0,0 +1,254 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2011.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
+
+Cu.import("resource:///modules/imServices.jsm");
+Cu.import("resource:///modules/imXPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "_", function()
+  l10nHelper("chrome://chat/locale/commands.properties")
+);
+
+function CommandsService() { }
+CommandsService.prototype = {
+  initCommands: function() {
+    this._commands = {};
+    // The say command is directly implemented in the UI layer, but has a
+    // dummy command registered here so it shows up as a command (e.g. when
+    // using the /help command).
+    this.registerCommand({
+      name: "say",
+      get helpString() _("sayHelpString"),
+      usageContext: Ci.imICommand.CONTEXT_ALL,
+      priority: Ci.imICommand.PRIORITY_HIGH,
+      run: function(aMsg, aConv) {
+        throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+      }
+    });
+
+    this.registerCommand({
+      name: "raw",
+      get helpString() _("rawHelpString"),
+      usageContext: Ci.imICommand.CONTEXT_ALL,
+      priority: Ci.imICommand.PRIORITY_DEFAULT,
+      run: function(aMsg, aConv) {
+        aConv.sendMsg(aMsg);
+        return true;
+      }
+    });
+
+    this.registerCommand({
+      // Reference the command service so we can use the internal properties
+      // directly.
+      cmdSrv: this,
+
+      name: "help",
+      get helpString() _("helpHelpString"),
+      usageContext: Ci.imICommand.CONTEXT_ALL,
+      priority: Ci.imICommand.PRIORITY_DEFAULT,
+      run: function(aMsg, aConv) {
+        let conv = Services.conversations.getUIConversation(aConv);
+        if (!conv)
+          return false;
+
+        // Handle when no command is given, list all possible commands that are
+        // available for this conversation (alphabetically).
+        if (!aMsg) {
+          let commands = this.cmdSrv.listCommandsForConversation(aConv, {});
+          if (!commands.length)
+            return false;
+
+          // Concatenate the command names (separated by a comma and space).
+          let cmds = commands.map(function(aCmd) aCmd.name).sort().join(", ");
+          let message = _("commands", cmds);
+
+          // Display the message
+          conv.systemMessage(message);
+          return true;
+        }
+
+        // A command name was given, find the commands that match.
+        let cmdArray = this.cmdSrv._findCommands(aConv, aMsg);
+
+        if (!cmdArray.length) {
+          // No command that matches.
+          let message = _("noCommand", aMsg);
+          conv.systemMessage(message);
+          return true;
+        }
+
+        // Only show the help for the one of the highest priority.
+        let cmd = cmdArray[0];
+
+        let text = cmd.helpString;
+        if (!text)
+          text = _("noHelp", cmd.name);
+
+        // Display the message.
+        conv.systemMessage(text);
+        return true;
+      }
+    });
+
+    // Status commands
+    let status = {
+      back: "AVAILABLE",
+      away: "AWAY",
+      busy: "UNAVAILABLE",
+      dnd: "UNAVAILABLE",
+      offline: "OFFLINE"
+    };
+    for (let cmd in status) {
+      let statusValue = Ci.imIStatusInfo["STATUS_" + status[cmd]];
+      this.registerCommand({
+        name: cmd,
+        get helpString() _("statusCommand", this.name, _(this.name)),
+        usageContext: Ci.imICommand.CONTEXT_ALL,
+        priority: Ci.imICommand.PRIORITY_HIGH,
+        run: function(aMsg) {
+          Services.core.globalUserStatus.setStatus(statusValue, aMsg);
+          return true;
+        }
+      });
+    }
+  },
+  unInitCommands: function() {
+    delete this._commands;
+  },
+
+  registerCommand: function(aCommand, aPrplId) {
+    let name = aCommand.name;
+    if (!name)
+      throw Cr.NS_ERROR_INVALID_ARG;
+
+    if (!(this._commands.hasOwnProperty(name)))
+      this._commands[name] = {};
+    this._commands[name][aPrplId || ""] = aCommand;
+  },
+  unregisterCommand: function(aCommandName, aPrplId) {
+    if (this._commands.hasOwnProperty(aCommandName)) {
+      let prplId = aPrplId || "";
+      let commands = this._commands[aCommandName];
+      if (commands.hasOwnProperty(aPrplId))
+        delete commands[aPrplId];
+      if (!Object.keys(commands).length)
+        delete this._commands[aCommandName];
+    }
+  },
+  listCommandsForConversation: function(aConversation, commandCount) {
+    let result = [];
+    let prplId = aConversation && aConversation.account.protocol.id;
+    for (let name in this._commands) {
+      let commands = this._commands[name];
+      if (commands.hasOwnProperty(""))
+        result.push(commands[""]);
+      if (prplId && commands.hasOwnProperty(prplId))
+        result.push(commands[prplId]);
+    }
+    result = result.filter(this._usageContextFilter(aConversation));
+    commandCount.value = result.length;
+    return result;
+  },
+  // List only the commands for a protocol (excluding the global commands).
+  listCommandsForProtocol: function(aPrplId, commandCount) {
+    if (!aPrplId)
+      throw "You must provide a prpl ID.";
+
+    let result = [];
+    for (let name in this._commands) {
+      let commands = this._commands[name];
+      if (commands.hasOwnProperty(aPrplId))
+        result.push(commands[aPrplId]);
+    }
+    commandCount.value = result.length;
+    return result;
+  },
+  _usageContextFilter: function(aConversation) {
+    let usageContext =
+      Ci.imICommand["CONTEXT_" + (aConversation.isChat ? "CHAT" : "IM")];
+    return function(c) c.usageContext & usageContext;
+  },
+  _findCommands: function(aConversation, aName) {
+    if (!(this._commands.hasOwnProperty(aName)))
+      return [];
+
+    // Get the 2 possible commands (the global and the proto specific)
+    let cmdArray = [];
+    let commands = this._commands[aName];
+    if (commands.hasOwnProperty(""))
+      cmdArray.push(commands[""]);
+
+    if (aConversation) {
+      let prplId = aConversation.account.protocol.id;
+      if (commands.hasOwnProperty(prplId))
+        cmdArray.push(commands[prplId]);
+    }
+
+    // Remove the commands that can't apply in this context.
+    cmdArray = cmdArray.filter(this._usageContextFilter(aConversation));
+
+    // Sort the matching commands by priority before returning the array.
+    return cmdArray.sort(function(a, b) b.priority - a.priority);
+  },
+  executeCommand: function (aMessage, aConversation) {
+    if (!aMessage)
+      throw Cr.NS_ERROR_INVALID_ARG;
+
+    let matchResult;
+    if (aMessage[0] != "/" ||
+        !(matchResult = /^\/([a-z]+)(?: |$)([\s\S]*)/.exec(aMessage)))
+      return false;
+
+    let [, name, args] = matchResult;
+
+    let cmdArray = this._findCommands(aConversation, name);
+    if (!cmdArray.length)
+      return false;
+
+    // cmdArray contains commands sorted by priority, attempt to apply
+    // them in order until one succeeds.
+    return cmdArray.some(function (aCmd) aCmd.run(args, aConversation));
+  },
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.imICommandsService]),
+  classDescription: "Commands",
+  classID: Components.ID("{7cb20c68-ccc8-4a79-b6f1-0b4771ed6c23}"),
+  contractID: "@mozilla.org/chat/commands-service;1"
+};
+
+const NSGetFactory = XPCOMUtils.generateNSGetFactory([CommandsService]);
new file mode 100644
--- /dev/null
+++ b/chat/components/src/imCommands.manifest
@@ -0,0 +1,2 @@
+component {7cb20c68-ccc8-4a79-b6f1-0b4771ed6c23} imCommands.js
+contract @mozilla.org/chat/commands-service;1 {7cb20c68-ccc8-4a79-b6f1-0b4771ed6c23}
new file mode 100644
--- /dev/null
+++ b/chat/components/src/imContacts.js
@@ -0,0 +1,1417 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2010.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+Cu.import("resource:///modules/imXPCOMUtils.jsm");
+Cu.import("resource:///modules/imServices.jsm");
+
+var gDBConnection = null;
+
+function getDBConnection()
+{
+  const NS_APP_USER_PROFILE_50_DIR = "ProfD";
+  let dbFile = Services.dirsvc.get(NS_APP_USER_PROFILE_50_DIR, Ci.nsIFile);
+  dbFile.append("blist.sqlite");
+
+  let conn =
+    Cc["@mozilla.org/storage/service;1"].getService(Ci.mozIStorageService)
+                                        .openDatabase(dbFile);
+  if (!conn.connectionReady)
+    throw Cr.NS_ERROR_UNEXPECTED;
+
+  // Grow blist db in 512KB increments.
+  conn.setGrowthIncrement(512 * 1024, "");
+
+  // Create tables and indexes.
+  [
+    "CREATE TABLE IF NOT EXISTS accounts (" +
+      "id INTEGER PRIMARY KEY, " +
+      "name VARCHAR, " +
+      "prpl VARCHAR)",
+
+    "CREATE TABLE IF NOT EXISTS contacts (" +
+      "id INTEGER PRIMARY KEY, " +
+      "firstname VARCHAR, " +
+      "lastname VARCHAR, " +
+      "alias VARCHAR)",
+
+    "CREATE TABLE IF NOT EXISTS buddies (" +
+      "id INTEGER PRIMARY KEY, " +
+      "key VARCHAR NOT NULL, " +
+      "name VARCHAR NOT NULL, " +
+      "srv_alias VARCHAR, " +
+      "position INTEGER, " +
+      "icon BLOB, " +
+      "contact_id INTEGER)",
+    "CREATE INDEX IF NOT EXISTS buddies_contactindex " +
+      "ON buddies (contact_id)",
+
+    "CREATE TABLE IF NOT EXISTS tags (" +
+      "id INTEGER PRIMARY KEY, " +
+      "name VARCHAR UNIQUE NOT NULL, " +
+      "position INTEGER)",
+
+    "CREATE TABLE IF NOT EXISTS contact_tag (" +
+      "contact_id INTEGER NOT NULL, " +
+      "tag_id INTEGER NOT NULL)",
+    "CREATE INDEX IF NOT EXISTS contact_tag_contactindex " +
+      "ON contact_tag (contact_id)",
+    "CREATE INDEX IF NOT EXISTS contact_tag_tagindex " +
+      "ON contact_tag (tag_id)",
+
+    "CREATE TABLE IF NOT EXISTS account_buddy (" +
+      "account_id INTEGER NOT NULL, " +
+      "buddy_id INTEGER NOT NULL, " +
+      "status VARCHAR, " +
+      "tag_id INTEGER)",
+    "CREATE INDEX IF NOT EXISTS account_buddy_accountindex " +
+      "ON account_buddy (account_id)",
+    "CREATE INDEX IF NOT EXISTS account_buddy_buddyindex " +
+      "ON account_buddy (buddy_id)"
+  ].forEach(conn.executeSimpleSQL);
+
+  return conn;
+}
+
+// Wrap all the usage of DBConn inside a transaction that will be
+// commited automatically at the end of the event loop spin so that
+// we flush buddy list data to disk only once per event loop spin.
+var gDBConnWithPendingTransaction = null;
+this.__defineGetter__("DBConn", function() {
+  if (gDBConnWithPendingTransaction)
+    return gDBConnWithPendingTransaction;
+
+  if (!gDBConnection)
+    gDBConnection = getDBConnection();
+  gDBConnWithPendingTransaction = gDBConnection;
+  gDBConnection.beginTransaction();
+  executeSoon(function() {
+    gDBConnWithPendingTransaction.commitTransaction();
+    gDBConnWithPendingTransaction = null;
+  });
+  return gDBConnection;
+});
+
+function TagsService() { }
+TagsService.prototype = {
+  get wrappedJSObject() this,
+  createTag: function(aName) {
+    // If the tag already exists, we don't want to create a duplicate.
+    let tag = this.getTagByName(aName);
+    if (tag)
+      return tag;
+
+    let statement = DBConn.createStatement("INSERT INTO tags (name, position) VALUES(:name, 0)");
+    statement.params.name = aName;
+    statement.executeStep();
+
+    tag = new Tag(DBConn.lastInsertRowID, aName);
+    Tags.push(tag);
+    return tag;
+  },
+  // Get an existing tag by (numeric) id. Returns null if not found.
+  getTagById: function(aId) TagsById[aId],
+  // Get an existing tag by name (will do an SQL query). Returns null
+  // if not found.
+  getTagByName: function(aName) {
+    let statement = DBConn.createStatement("SELECT id FROM tags where name = :name");
+    statement.params.name = aName;
+    if (!statement.executeStep())
+      return null;
+    return this.getTagById(statement.row.id);
+  },
+  // Get an array of all existing tags.
+  getTags: function(aTagCount) {
+    if (aTagCount)
+      aTagCount.value = Tags.length;
+    return Tags;
+  },
+
+  isTagHidden: function(aTag) aTag.id in otherContactsTag._hiddenTags,
+  hideTag: function(aTag) { otherContactsTag.hideTag(aTag); },
+  showTag: function(aTag) { otherContactsTag.showTag(aTag); },
+  get otherContactsTag() {
+    otherContactsTag._initContacts();
+    return otherContactsTag;
+  },
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.imITagsService]),
+  classDescription: "Tags",
+  classID: Components.ID("{1fa92237-4303-4384-b8ac-4e65b50810a5}"),
+  contractID: "@mozilla.org/chat/tags-service;1"
+};
+
+// TODO move into the tagsService
+var Tags = [];
+var TagsById = { };
+
+function Tag(aId, aName) {
+  this._id = aId;
+  this._name = aName;
+  this._contacts = [];
+  this._observers = [];
+
+  TagsById[this.id] = this;
+}
+Tag.prototype = {
+  get id() this._id,
+  get name() this._name,
+  set name(aNewName) {
+    let statement = DBConn.createStatement("UPDATE tags SET name = :name WHERE id = :id");
+    statement.params.name = aNewName;
+    statement.params.id = this._id;
+    statement.execute();
+
+    //FIXME move the account buddies if some use this tag as their group
+    return aNewName;
+  },
+  getContacts: function(aContactCount) {
+    let contacts = this._contacts.filter(function(c) !c._empty);
+    if (aContactCount)
+      aContactCount.value = contacts.length;
+    return contacts;
+  },
+  _addContact: function (aContact) {
+    this._contacts.push(aContact);
+  },
+  _removeContact: function (aContact) {
+    let index = this._contacts.indexOf(aContact);
+    if (index != -1)
+      this._contacts.splice(index, 1);
+  },
+
+  addObserver: function(aObserver) {
+    if (this._observers.indexOf(aObserver) == -1)
+      this._observers.push(aObserver);
+  },
+  removeObserver: function(aObserver) {
+    this._observers = this._observers.filter(function(o) o !== aObserver);
+  },
+  notifyObservers: function(aSubject, aTopic, aData) {
+    for each (let observer in this._observers)
+      observer.observe(aSubject, aTopic, aData);
+  },
+
+  getInterfaces: function(countRef) {
+    let interfaces = [Ci.nsIClassInfo, Ci.nsISupports, Ci.imITag];
+    countRef.value = interfaces.length;
+    return interfaces;
+  },
+  getHelperForLanguage: function(language) null,
+  implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
+  flags: 0,
+  QueryInterface: XPCOMUtils.generateQI([Ci.imITag, Ci.nsIClassInfo])
+};
+
+
+var otherContactsTag = {
+  hiddenTagsPref: "messenger.buddies.hiddenTags",
+  _hiddenTags: {},
+  _contactsInitialized: false,
+  _saveHiddenTagsPref: function() {
+    Services.prefs.setCharPref(this.hiddenTagsPref,
+                               [id for (id in this._hiddenTags)].join(","));
+  },
+  showTag: function(aTag) {
+    let id = aTag.id;
+    delete this._hiddenTags[id];
+    for each (let contact in this._contacts)
+      if (contact.getTags().some(function(t) t.id == id))
+        this._removeContact(contact);
+
+    aTag.notifyObservers(aTag, "tag-shown", null);
+    Services.obs.notifyObservers(aTag, "tag-shown", null);
+    this._saveHiddenTagsPref();
+  },
+  hideTag: function(aTag) {
+    if (aTag.id < 0 || aTag.id in otherContactsTag._hiddenTags)
+      return;
+
+    this._hiddenTags[aTag.id] = aTag;
+    if (this._contactsInitialized)
+      this._hideTag(aTag);
+
+    aTag.notifyObservers(aTag, "tag-hidden", null);
+    Services.obs.notifyObservers(aTag, "tag-hidden", null);
+    this._saveHiddenTagsPref();
+  },
+  _hideTag: function(aTag) {
+    for each (let contact in aTag.getContacts())
+      if (!(contact.id in this._contacts) &&
+          contact.getTags().every(function(t) t.id in this._hiddenTags, this))
+        this._addContact(contact);
+  },
+  observe: function(aSubject, aTopic, aData) {
+    aSubject.QueryInterface(Ci.imIContact);
+    if (aTopic == "contact-tag-removed") {
+      if (!(aSubject.id in this._contacts) &&
+          !(parseInt(aData) in this._hiddenTags) &&
+          aSubject.getTags().every(function(t) t.id in this._hiddenTags, this))
+        this._addContact(aSubject);
+    }
+    else if (aSubject.id in this._contacts &&
+             (aTopic == "contact-removed" ||
+              (aTopic == "contact-tag-added" &&
+              !(parseInt(aData) in this._hiddenTags))))
+      this._removeContact(aSubject);
+  },
+
+  _initHiddenTags: function() {
+    let pref = Services.prefs.getCharPref(this.hiddenTagsPref);
+    if (!pref)
+      return;
+    for each (let tagId in pref.split(","))
+      this._hiddenTags[tagId] = TagsById[tagId];
+  },
+  _initContacts: function() {
+    if (this._contactsInitialized)
+      return;
+    this._observers = [];
+    this._observer = {
+      self: this,
+      observe: function(aSubject, aTopic, aData) {
+        if (aTopic == "contact-moved-in" && !(aSubject instanceof Contact))
+          return;
+
+        this.self.notifyObservers(aSubject, aTopic, aData);
+      }
+    };
+    this._contacts = {};
+    this._contactsInitialized = true;
+    for each (let tag in this._hiddenTags)
+      this._hideTag(tag);
+    Services.obs.addObserver(this, "contact-tag-added", false);
+    Services.obs.addObserver(this, "contact-tag-removed", false);
+    Services.obs.addObserver(this, "contact-removed", false);
+  },
+
+  // imITag implementation
+  get id() -1,
+  get name() "__others__",
+  set name(aNewName) { throw Cr.NS_ERROR_NOT_AVAILABLE; },
+  getContacts: function(aContactCount) {
+    let contacts = [contact for each (contact in this._contacts)];
+    if (aContactCount)
+      aContactCount.value = contacts.length;
+    return contacts;
+  },
+  _addContact: function (aContact) {
+    this._contacts[aContact.id] = aContact;
+    this.notifyObservers(aContact, "contact-moved-in");
+    for each (let observer in ContactsById[aContact.id]._observers)
+      observer.observe(this, "contact-moved-in", null);
+    aContact.addObserver(this._observer);
+  },
+  _removeContact: function (aContact) {
+    delete this._contacts[aContact.id];
+    aContact.removeObserver(this._observer);
+    this.notifyObservers(aContact, "contact-moved-out");
+    for each (let observer in ContactsById[aContact.id]._observers)
+      observer.observe(this, "contact-moved-out", null);
+  },
+
+  addObserver: function(aObserver) {
+    if (this._observers.indexOf(aObserver) == -1)
+      this._observers.push(aObserver);
+  },
+  removeObserver: function(aObserver) {
+    this._observers = this._observers.filter(function(o) o !== aObserver);
+  },
+  notifyObservers: function(aSubject, aTopic, aData) {
+    for each (let observer in this._observers)
+      observer.observe(aSubject, aTopic, aData);
+  },
+
+  getInterfaces: function(countRef) {
+    let interfaces = [Ci.nsIClassInfo, Ci.nsISupports, Ci.nsIObserver, Ci.imITag];
+    countRef.value = interfaces.length;
+    return interfaces;
+  },
+  getHelperForLanguage: function(language) null,
+  implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
+  flags: 0,
+  QueryInterface: XPCOMUtils.generateQI([Ci.imITag, Ci.nsIObserver, Ci.nsIClassInfo])
+};
+
+
+var ContactsById = { };
+var LastDummyContactId = 0;
+function Contact(aId, aAlias) {
+  // Assign a negative id to dummy contacts that have a single buddy
+  this._id = aId || --LastDummyContactId;
+  this._alias = aAlias;
+  this._tags = [];
+  this._buddies = [];
+  this._observers = [];
+
+  ContactsById[this._id] = this;
+}
+Contact.prototype = {
+  _id: 0,
+  get id() this._id,
+  get alias() this._alias,
+  set alias(aNewAlias) {
+    this._ensureNotDummy();
+
+    let statement = DBConn.createStatement("UPDATE contacts SET alias = :alias WHERE id = :id");
+    statement.params.alias = aNewAlias;
+    statement.params.id = this._id;
+    statement.executeAsync();
+
+    let oldDisplayName = this.displayName;
+    this._alias = aNewAlias;
+    this._notifyObservers("display-name-changed", oldDisplayName);
+    for each (let buddy in this._buddies)
+      for each (let accountBuddy in buddy._accounts)
+        accountBuddy.serverAlias = aNewAlias;
+    return aNewAlias;
+  },
+  _ensureNotDummy: function() {
+    if (this._id >= 0)
+      return;
+
+    // Create a real contact for this dummy contact
+    let statement = DBConn.createStatement("INSERT INTO contacts DEFAULT VALUES");
+    statement.execute();
+    delete ContactsById[this._id];
+    let oldId = this._id;
+    this._id = DBConn.lastInsertRowID;
+    ContactsById[this._id] = this;
+    this._notifyObservers("no-longer-dummy", oldId.toString());
+    // Update the contact_id for the single existing buddy of this contact
+    statement = DBConn.createStatement("UPDATE buddies SET contact_id = :id WHERE id = :buddy_id");
+    statement.params.id = this._id;
+    statement.params.buddy_id = this._buddies[0].id;
+    statement.executeAsync();
+  },
+
+  getTags: function(aTagCount) {
+    if (aTagCount)
+      aTagCount.value = this._tags.length;
+    return this._tags;
+  },
+  addTag: function(aTag, aInherited) {
+    if (this.hasTag(aTag))
+      return;
+
+    if (!aInherited) {
+      this._ensureNotDummy();
+      let statement =
+        DBConn.createStatement("INSERT INTO contact_tag (contact_id, tag_id) " +
+                               "VALUES(:contactId, :tagId)");
+      statement.params.contactId = this.id;
+      statement.params.tagId = aTag.id;
+      statement.executeAsync();
+    }
+
+    aTag = TagsById[aTag.id];
+    this._tags.push(aTag);
+    aTag._addContact(this);
+
+    aTag.notifyObservers(this, "contact-moved-in");
+    for each (let observer in this._observers)
+      observer.observe(aTag, "contact-moved-in", null);
+    Services.obs.notifyObservers(this, "contact-tag-added", aTag.id);
+  },
+  /* Remove a tag from the local tags of the contact. */
+  _removeTag: function(aTag) {
+    if (!this.hasTag(aTag) || this._isTagInherited(aTag))
+      return;
+
+    let statement = DBConn.createStatement("DELETE FROM contact_tag " +
+                                           "WHERE contact_id = :contactId " +
+                                           "AND tag_id = :tagId");
+    statement.params.contactId = this.id;
+    statement.params.tagId = aTag.id;
+    statement.executeAsync();
+
+    this._tags = this._tags.filter(function(tag) tag.id != aTag.id);
+    aTag = TagsById[aTag.id];
+    aTag._removeContact(this);
+
+    aTag.notifyObservers(this, "contact-moved-out");
+    for each (let observer in this._observers)
+      observer.observe(aTag, "contact-moved-out", null);
+    Services.obs.notifyObservers(this, "contact-tag-removed", aTag.id);
+  },
+  hasTag: function(aTag) this._tags.some(function (t) t.id == aTag.id),
+  _massMove: false,
+  removeTag: function(aTag) {
+    if (!this.hasTag(aTag))
+      throw "Attempting to remove a tag that the contact doesn't have";
+    if (this._tags.length == 1)
+      throw "Attempting to remove the last tag of a contact";
+
+    this._massMove = true;
+    let hasTag = this.hasTag.bind(this);
+    let newTag = this._tags[this._tags[0].id != aTag.id ? 0 : 1];
+    let moved = false;
+    this._buddies.forEach(function (aBuddy) {
+      aBuddy._accounts.forEach(function (aAccountBuddy) {
+        if (aAccountBuddy.tag.id == aTag.id) {
+          if (aBuddy._accounts.some(function(ab)
+               ab.account.numericId == aAccountBuddy.account.numericId &&
+               ab.tag.id != aTag.id && hasTag(ab.tag))) {
+            // A buddy that already has an accountBuddy of the same
+            // account with another tag of the contact shouldn't be
+            // moved to newTag, just remove the accountBuddy
+            // associated to the tag we are removing.
+            aAccountBuddy.remove();
+            moved = true;
+          }
+          else {
+            try {
+              aAccountBuddy.tag = newTag;
+              moved = true;
+            } catch (e) {
+              // Ignore failures. Some protocol plugins may not implement this.
+            }
+          }
+        }
+      });
+    });
+    this._massMove = false;
+    if (moved)
+      this._moved(aTag, newTag);
+    else {
+      // If we are here, the old tag is not inherited from a buddy, so
+      // just remove the local tag.
+      this._removeTag(aTag);
+    }
+  },
+  _isTagInherited: function(aTag) {
+    for each (let buddy in this._buddies)
+      for each (let accountBuddy in buddy._accounts)
+        if (accountBuddy.tag.id == aTag.id)
+          return true;
+    return false;
+  },
+  _moved: function(aOldTag, aNewTag) {
+    if (this._massMove)
+      return;
+
+    // Avoid xpconnect wrappers.
+    aNewTag = aNewTag && TagsById[aNewTag.id];
+    aOldTag = aOldTag && TagsById[aOldTag.id];
+
+    // Decide what we need to do. Return early if nothing to do.
+    let shouldRemove =
+      aOldTag && this.hasTag(aOldTag) && !this._isTagInherited(aOldTag);
+    let shouldAdd =
+      aNewTag && !this.hasTag(aNewTag) && this._isTagInherited(aNewTag);
+    if (!shouldRemove && !shouldAdd)
+      return;
+
+    // Apply the changes.
+    let tags = this._tags;
+    if (shouldRemove) {
+      tags = tags.filter(function(aTag) aTag.id != aOldTag.id);
+      aOldTag._removeContact(this);
+    }
+    if (shouldAdd) {
+      tags.push(aNewTag);
+      aNewTag._addContact(this);
+    }
+    this._tags = tags;
+
+    // Finally, notify of the changes.
+    if (shouldRemove) {
+      aOldTag.notifyObservers(this, "contact-moved-out");
+      for each (let observer in this._observers)
+        observer.observe(aOldTag, "contact-moved-out", null);
+      Services.obs.notifyObservers(this, "contact-tag-removed", aOldTag.id);
+    }
+    if (shouldAdd) {
+      aNewTag.notifyObservers(this, "contact-moved-in");
+      for each (let observer in this._observers)
+        observer.observe(aNewTag, "contact-moved-in", null);
+      Services.obs.notifyObservers(this, "contact-tag-added", aNewTag.id);
+    }
+    Services.obs.notifyObservers(this, "contact-moved", null);
+  },
+
+  getBuddies: function(aBuddyCount) {
+    if (aBuddyCount)
+      aBuddyCount.value = this._buddies.length;
+    return this._buddies;
+  },
+  get _empty() this._buddies.length == 0 ||
+               this._buddies.every(function(b) b._empty),
+
+  mergeContact: function(aContact) {
+    // Avoid merging the contact with itself or merging into an
+    // already removed contact.
+    if (aContact.id == this.id || !(this.id in ContactsById))
+      throw Components.results.NS_ERROR_INVALID_ARG;
+
+    this._ensureNotDummy();
+    let contact = ContactsById[aContact.id]; // remove XPConnect wrapper
+
+    // Copy all the contact-only tags first, otherwise they would be lost.
+    for each (let tag in contact.getTags())
+      if (!contact._isTagInherited(tag))
+        this.addTag(tag);
+
+    // Adopt each buddy. Removing the last one will delete the contact.
+    for each (let buddy in contact.getBuddies())
+      buddy.contact = this;
+    this._updatePreferredBuddy();
+  },
+  moveBuddyBefore: function(aBuddy, aBeforeBuddy) {
+    let buddy = BuddiesById[aBuddy.id]; // remove XPConnect wrapper
+    let oldPosition = this._buddies.indexOf(buddy);
+    if (oldPosition == -1)
+      throw "aBuddy isn't attached to this contact";
+
+    let newPosition = -1;
+    if (aBeforeBuddy)
+      newPosition = this._buddies.indexOf(BuddiesById[aBeforeBuddy.id]);
+    if (newPosition == -1)
+      newPosition = this._buddies.length - 1;
+
+    if (oldPosition == newPosition)
+      return;
+
+    this._buddies.splice(oldPosition, 1);
+    this._buddies.splice(newPosition, 0, buddy);
+    this._updatePositions(Math.min(oldPosition, newPosition),
+                          Math.max(oldPosition, newPosition));
+    buddy._notifyObservers("position-changed", String(newPosition));
+    this._updatePreferredBuddy(buddy);
+  },
+  adoptBuddy: function(aBuddy) {
+    if (aBuddy.contact.id == this.id)
+      throw Components.results.NS_ERROR_INVALID_ARG;
+
+    let buddy = BuddiesById[aBuddy.id]; // remove XPConnect wrapper
+    buddy.contact = this;
+    this._updatePreferredBuddy(buddy);
+  },
+  _massRemove: false,
+  _removeBuddy: function(aBuddy) {
+    if (this._buddies.length == 1) {
+      if (this._id > 0) {
+        let statement =
+          DBConn.createStatement("DELETE FROM contacts WHERE id = :id");
+        statement.params.id = this._id;
+        statement.executeAsync();
+      }
+      this._notifyObservers("removed");
+      delete ContactsById[this._id];
+
+      for each (let tag in this._tags)
+        tag._removeContact(this);
+      let statement =
+        DBConn.createStatement("DELETE FROM contact_tag WHERE contact_id = :id");
+      statement.params.id = this._id;
+      statement.executeAsync();
+
+      delete this._tags;
+      delete this._buddies;
+      delete this._observers;
+    }
+    else {
+      let index = this._buddies.indexOf(aBuddy);
+      if (index == -1)
+        throw "Removing an unknown buddy from contact " + this._id;
+
+      this._buddies = this._buddies.filter(function(b) b !== aBuddy);
+
+      // If we are actually removing the whole contact, don't bother updating
+      // the positions or the preferred buddy.
+      if (this._massRemove)
+        return;
+
+      // No position to update if the removed buddy is at the last position.
+      if (index < this._buddies.length)
+        this._updatePositions(index);
+
+      if (this._preferredBuddy.id == aBuddy.id)
+        this._updatePreferredBuddy();
+    }
+  },
+  _updatePositions: function(aIndexBegin, aIndexEnd) {
+    if (aIndexEnd === undefined)
+      aIndexEnd = this._buddies.length - 1;
+    if (aIndexBegin > aIndexEnd)
+      throw "_updatePositions: Invalid indexes";
+
+    let statement =
+      DBConn.createStatement("UPDATE buddies SET position = :position " +
+                             "WHERE id = :buddyId");
+    for (let i = aIndexBegin; i <= aIndexEnd; ++i) {
+      statement.params.position = i;
+      statement.params.buddyId = this._buddies[i].id;
+      statement.executeAsync();
+    }
+  },
+
+  detachBuddy: function(aBuddy) {
+    // Should return a new contact with the same list of tags.
+    let buddy = BuddiesById[aBuddy.id];
+    if (buddy.contact.id != this.id)
+      throw Components.results.NS_ERROR_INVALID_ARG;
+    if (buddy.contact._buddies.length == 1)
+      throw Components.results.NS_ERROR_UNEXPECTED;
+
+    // Save the list of tags, it may be destoyed if the buddy was the last one.
+    let tags = buddy.contact.getTags();
+
+    // Create a new dummy contact and use it for the detached buddy.
+    buddy.contact = new Contact();
+
+    // The first tag was inherited during the contact setter.
+    // This will copy the remaining tags.
+    for each (let tag in tags)
+      buddy.contact.addTag(tag);
+
+    return buddy.contact;
+  },
+  remove: function() {
+    this._massRemove = true;
+    for each (let buddy in this._buddies)
+      buddy.remove();
+  },
+
+  // imIStatusInfo implementation
+  _preferredBuddy: null,
+  get preferredBuddy() {
+    if (!this._preferredBuddy)
+      this._updatePreferredBuddy();
+    return this._preferredBuddy;
+  },
+  set preferredBuddy(aBuddy) {
+    let shouldNotify = this._preferredBuddy != null;
+    let oldDisplayName =
+      this._preferredBuddy && this._preferredBuddy.displayName;
+    this._preferredBuddy = aBuddy;
+    if (shouldNotify)
+      this._notifyObservers("preferred-buddy-changed");
+    if (oldDisplayName && this._preferredBuddy.displayName != oldDisplayName)
+      this._notifyObservers("display-name-changed", oldDisplayName);
+    this._updateStatus();
+  },
+  // aBuddy indicate which buddy's availability has changed.
+  _updatePreferredBuddy: function(aBuddy) {
+    if (aBuddy) {
+      aBuddy = BuddiesById[aBuddy.id]; // remove potential XPConnect wrapper
+
+      if (!this._preferredBuddy) {
+        this.preferredBuddy = aBuddy;
+        return;
+      }
+
+      if (aBuddy.id == this._preferredBuddy.id) {
+        // The suggested buddy is already preferred, check if its
+        // availability has changed.
+        if (aBuddy.statusType > this._statusType ||
+            (aBuddy.statusType == this._statusType &&
+             aBuddy.availabilityDetails >= this._availabilityDetails)) {
+          // keep the currently preferred buddy, only update the status.
+          this._updateStatus();
+          return;
+        }
+        // We aren't sure that the currently preferred buddy should
+        // still be preferred. Let's go through the list!
+      }
+      else {
+        // The suggested buddy is not currently preferred. If it is
+        // more available or at a better position, prefer it!
+        if (aBuddy.statusType > this._statusType ||
+            (aBuddy.statusType == this._statusType &&
+             (aBuddy.availabilityDetails > this._availabilityDetails ||
+              (aBuddy.availabilityDetails == this._availabilityDetails &&
+               this._buddies.indexOf(aBuddy) < this._buddies.indexOf(this.preferredBuddy)))))
+          this.preferredBuddy = aBuddy;
+        return;
+      }
+    }
+
+    let preferred;
+    // |this._buddies| is ordered by user preference, so in case of
+    // equal availability, keep the current value of |preferred|.
+    for each (let buddy in this._buddies) {
+      if (!preferred || preferred.statusType < buddy.statusType ||
+          (preferred.statusType == buddy.statusType &&
+           preferred.availabilityDetails < buddy.availabilityDetails))
+        preferred = buddy;
+    }
+    if (preferred && (!this._preferredBuddy ||
+                      preferred.id != this._preferredBuddy.id))
+      this.preferredBuddy = preferred;
+  },
+  _updateStatus: function() {
+    let buddy = this._preferredBuddy; // for convenience
+
+    // Decide which notifications should be fired.
+    let notifications = [];
+    if (this._statusType != buddy.statusType ||
+        this._availabilityDetails != buddy.availabilityDetails)
+      notifications.push("availability-changed");
+    if (this._statusType != buddy.statusType ||
+        this._statusText != buddy.statusText) {
+      notifications.push("status-changed");
+      if (this.online && buddy.statusType <= Ci.imIStatusInfo.STATUS_OFFLINE)
+        notifications.push("signed-off");
+      if (!this.online && buddy.statusType > Ci.imIStatusInfo.STATUS_OFFLINE)
+        notifications.push("signed-on");
+    }
+
+    // Actually change the stored status.
+    [this._statusType, this._statusText, this._availabilityDetails] =
+      [buddy.statusType, buddy.statusText, buddy.availabilityDetails];
+
+    // Fire the notifications.
+    notifications.forEach(function(aTopic) {
+      this._notifyObservers(aTopic);
+    }, this);
+  },
+  get displayName() this._alias || this.preferredBuddy.displayName,
+  get buddyIconFilename() this.preferredBuddy.buddyIconFilename,
+  _statusType: 0,
+  get statusType() this._statusType,
+  get online() this.statusType > Ci.imIStatusInfo.STATUS_OFFLINE,
+  get available() this.statusType == Ci.imIStatusInfo.STATUS_AVAILABLE,
+  get idle() this.statusType == Ci.imIStatusInfo.STATUS_IDLE,
+  get mobile() this.statusType == Ci.imIStatusInfo.STATUS_MOBILE,
+  _statusText: "",
+  get statusText() this._statusText,
+  _availabilityDetails: 0,
+  get availabilityDetails() this._availabilityDetails,
+  get canSendMessage() this.preferredBuddy.canSendMessage,
+  //XXX should we list the buddies in the tooltip?
+  getTooltipInfo: function() this.preferredBuddy.getTooltipInfo(),
+  createConversation: function() {
+    let uiConv = Services.conversations.getUIConversationByContactId(this.id);
+    if (uiConv)
+      return uiConv.target;
+    return this.preferredBuddy.createConversation();
+  },
+
+  addObserver: function(aObserver) {
+    if (this._observers.indexOf(aObserver) == -1)
+      this._observers.push(aObserver);
+  },
+  removeObserver: function(aObserver) {
+    if (!this.hasOwnProperty("_observers"))
+      return;
+
+    this._observers = this._observers.filter(function(o) o !== aObserver);
+  },
+  // internal calls + calls from add-ons
+  notifyObservers: function(aSubject, aTopic, aData) {
+    for each (let observer in this._observers)
+      if ("observe" in observer) // avoid failing on destructed XBL bindings...
+        observer.observe(aSubject, aTopic, aData);
+    for each (let tag in this._tags)
+      tag.notifyObservers(aSubject, aTopic, aData);
+    Services.obs.notifyObservers(aSubject, aTopic, aData);
+  },
+  _notifyObservers: function(aTopic, aData) {
+    this.notifyObservers(this, "contact-" + aTopic, aData);
+  },
+
+  // This is called by the imIBuddy implementations.
+  _observe: function(aSubject, aTopic, aData) {
+    // Forward the notification.
+    this.notifyObservers(aSubject, aTopic, aData);
+
+    let isPreferredBuddy =
+      aSubject instanceof Buddy && aSubject.id == this.preferredBuddy.id;
+    switch (aTopic) {
+      case "buddy-availability-changed":
+        this._updatePreferredBuddy(aSubject);
+        break;
+      case "buddy-status-changed":
+        if (isPreferredBuddy)
+          this._updateStatus();
+        break;
+      case "buddy-display-name-changed":
+        if (isPreferredBuddy && !this._alias)
+          this._notifyObservers("display-name-changed", aData);
+        break;
+      case "buddy-icon-changed":
+        if (isPreferredBuddy)
+          this._notifyObservers("icon-changed");
+        break;
+      case "buddy-added":
+        // Currently buddies are always added in dummy empty contacts,
+        // later we may want to check this._buddies.length == 1.
+        this._notifyObservers("added");
+        break;
+      case "buddy-removed":
+        this._removeBuddy(aSubject);
+    }
+  },
+
+  getInterfaces: function(countRef) {
+    let interfaces = [Ci.nsIClassInfo, Ci.nsISupports, Ci.imIContact];
+    countRef.value = interfaces.length;
+    return interfaces;
+  },
+  getHelperForLanguage: function(language) null,
+  implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
+  flags: 0,
+  QueryInterface: XPCOMUtils.generateQI([Ci.imIContact, Ci.nsIClassInfo])
+};
+
+var BuddiesById = { };
+function Buddy(aId, aKey, aName, aSrvAlias, aContactId) {
+  this._id = aId;
+  this._key = aKey;
+  this._name = aName;
+  if (aSrvAlias)
+    this._srvAlias = aSrvAlias;
+  this._accounts = [];
+  this._observers = [];
+
+  if (aContactId)
+    this._contact = ContactsById[aContactId];
+  // Avoid failure if aContactId was invalid.
+  if (!this._contact)
+    this._contact = new Contact(null, null);
+
+  this._contact._buddies.push(this);
+
+  BuddiesById[this._id] = this;
+}
+Buddy.prototype = {
+  get id() this._id,
+  destroy: function() {
+    for each (let ab in this._accounts)
+      ab.unInit();
+    delete this._accounts;
+    delete this._observers;
+    delete this._preferredAccount;
+  },
+  get protocol() this._accounts[0].account.protocol,
+  get userName() this._name,
+  get normalizedName() this._key,
+  _srvAlias: "",
+  _contact: null,
+  get contact() this._contact,
+  set contact(aContact) /* not in imIBuddy */ {
+    if (aContact.id == this._contact.id)
+      throw Components.results.NS_ERROR_INVALID_ARG;
+
+    this._notifyObservers("moved-out-of-contact");
+    this._contact._removeBuddy(this);
+
+    this._contact = aContact;
+    this._contact._buddies.push(this);
+
+    // Ensure all the inherited tags are in the new contact.
+    for each (let accountBuddy in this._accounts)
+      this._contact.addTag(TagsById[accountBuddy.tag.id], true);
+
+    let statement =
+      DBConn.createStatement("UPDATE buddies SET contact_id = :contactId, " +
+                             "position = :position " +
+                             "WHERE id = :buddyId");
+    statement.params.contactId = aContact.id > 0 ? aContact.id : 0;
+    statement.params.position = aContact._buddies.length - 1;
+    statement.params.buddyId = this.id;
+    statement.executeAsync();
+
+    this._notifyObservers("moved-into-contact");
+    return aContact;
+  },
+  _hasAccountBuddy: function(aAccountId, aTagId) {
+    for each (let ab in this._accounts) {
+      if (ab.account.numericId == aAccountId && ab.tag.id == aTagId)
+        return true;
+    }
+    return false;
+  },
+  getAccountBuddies: function(aAccountBuddyCount) {
+    if (aAccountBuddyCount)
+      aAccountBuddyCount.value = this._accounts.length;
+    return this._accounts;
+  },
+
+  _addAccount: function(aAccountBuddy, aTag) {
+    this._accounts.push(aAccountBuddy);
+    let contact = this._contact;
+    if (this._contact._tags.indexOf(aTag) == -1) {
+      this._contact._tags.push(aTag);
+      aTag._addContact(contact);
+    }
+
+    if (!this._preferredAccount)
+      this._preferredAccount = aAccountBuddy;
+  },
+  get _empty() this._accounts.length == 0,
+
+  remove: function() {
+    for each (let account in this._accounts)
+      account.remove();
+  },
+
+  // imIStatusInfo implementation
+  _preferredAccount: null,
+  get preferredAccountBuddy() this._preferredAccount,
+  _isPreferredAccount: function(aAccountBuddy) {
+    if (aAccountBuddy.account.numericId != this._preferredAccount.account.numericId)
+      return false;
+
+    // In case we have more than one accountBuddy for the same buddy
+    // and account (possible if the buddy is in several groups on the
+    // server), the protocol plugin may be broken and not update all
+    // instances, so ensure we handle the notifications on the instance
+    // that is currently being notified of a change:
+    this._preferredAccount = aAccountBuddy;
+
+    return true;
+  },
+  set preferredAccount(aAccount) {
+    let oldDisplayName =
+      this._preferredAccount && this._preferredAccount.displayName;
+    this._preferredAccount = aAccount;
+    this._notifyObservers("preferred-account-changed");
+    if (oldDisplayName && this._preferredAccount.displayName != oldDisplayName)
+      this._notifyObservers("display-name-changed", oldDisplayName);
+    this._updateStatus();
+  },
+  // aAccount indicate which account's availability has changed.
+  _updatePreferredAccount: function(aAccount) {
+    if (aAccount) {
+      if (aAccount.account.numericId == this._preferredAccount.account.numericId) {
+        // The suggested account is already preferred, check if its
+        // availability has changed.
+        if (aAccount.statusType > this._statusType ||
+            (aAccount.statusType == this._statusType &&
+             aAccount.availabilityDetails >= this._availabilityDetails)) {
+          // keep the currently preferred account, only update the status.
+          this._updateStatus();
+          return;
+        }
+        // We aren't sure that the currently preferred account should
+        // still be preferred. Let's go through the list!
+      }
+      else {
+        // The suggested account is not currently preferred. If it is
+        // more available, prefer it!
+        if (aAccount.statusType > this._statusType ||
+            (aAccount.statusType == this._statusType &&
+             aAccount.availabilityDetails > this._availabilityDetails))
+          this.preferredAccount = aAccount;
+        return;
+      }
+    }
+
+    let preferred;
+    //TODO take into account the order of the account-manager list.
+    for each (let account in this._accounts) {
+      if (!preferred || preferred.statusType < account.statusType ||
+          (preferred.statusType == account.statusType &&
+           preferred.availabilityDetails < account.availabilityDetails))
+        preferred = account;
+    }
+    if (!this._preferredAccount) {
+      if (preferred)
+        this.preferredAccount = preferred;
+      return;
+    }
+    if (preferred.account.numericId != this._preferredAccount.account.numericId)
+      this.preferredAccount = preferred;
+    else
+      this._updateStatus();
+  },
+  _updateStatus: function() {
+    let account = this._preferredAccount; // for convenience
+
+    // Decide which notifications should be fired.
+    let notifications = [];
+    if (this._statusType != account.statusType ||
+        this._availabilityDetails != account.availabilityDetails)
+      notifications.push("availability-changed");
+    if (this._statusType != account.statusType ||
+        this._statusText != account.statusText) {
+      notifications.push("status-changed");
+      if (this.online && account.statusType <= Ci.imIStatusInfo.STATUS_OFFLINE)
+        notifications.push("signed-off");
+      if (!this.online && account.statusType > Ci.imIStatusInfo.STATUS_OFFLINE)
+        notifications.push("signed-on");
+    }
+
+    // Actually change the stored status.
+    [this._statusType, this._statusText, this._availabilityDetails] =
+      [account.statusType, account.statusText, account.availabilityDetails];
+
+    // Fire the notifications.
+    notifications.forEach(function(aTopic) {
+      this._notifyObservers(aTopic);
+    }, this);
+  },
+  get displayName() this._preferredAccount && this._preferredAccount.displayName ||
+                    this._srvAlias || this._name,
+  get buddyIconFilename() this._preferredAccount.buddyIconFilename,
+  _statusType: 0,
+  get statusType() this._statusType,
+  get online() this.statusType > Ci.imIStatusInfo.STATUS_OFFLINE,
+  get available() this.statusType == Ci.imIStatusInfo.STATUS_AVAILABLE,
+  get idle() this.statusType == Ci.imIStatusInfo.STATUS_IDLE,
+  get mobile() this.statusType == Ci.imIStatusInfo.STATUS_MOBILE,
+  _statusText: "",
+  get statusText() this._statusText,
+  _availabilityDetails: 0,
+  get availabilityDetails() this._availabilityDetails,
+  get canSendMessage() this._preferredAccount.canSendMessage,
+  //XXX should we list the accounts in the tooltip?
+  getTooltipInfo: function() this._preferredAccount.getTooltipInfo(),
+  createConversation: function() this._preferredAccount.createConversation(),
+
+  addObserver: function(aObserver) {
+    if (this._observers.indexOf(aObserver) == -1)
+      this._observers.push(aObserver);
+  },
+  removeObserver: function(aObserver) {
+    this._observers = this._observers.filter(function(o) o !== aObserver);
+  },
+  // internal calls + calls from add-ons
+  notifyObservers: function(aSubject, aTopic, aData) {
+    try {
+      for each (let observer in this._observers)
+        observer.observe(aSubject, aTopic, aData);
+      this._contact._observe(aSubject, aTopic, aData);
+    } catch (e) {
+      Cu.reportError(e);
+    }
+  },
+  _notifyObservers: function(aTopic, aData) {
+    this.notifyObservers(this, "buddy-" + aTopic, aData);
+  },
+
+  // This is called by the imIAccountBuddy implementations.
+  observe: function(aSubject, aTopic, aData) {
+    // Forward the notification.
+    this.notifyObservers(aSubject, aTopic, aData);
+
+    switch (aTopic) {
+      case "account-buddy-availability-changed":
+        this._updatePreferredAccount(aSubject);
+        break;
+      case "account-buddy-status-changed":
+        if (this._isPreferredAccount(aSubject))
+          this._updateStatus();
+        break;
+      case "account-buddy-display-name-changed":
+        if (this._isPreferredAccount(aSubject)) {
+          this._srvAlias =
+            this.displayName != this.userName ? this.displayName : "";
+          let statement =
+            DBConn.createStatement("UPDATE buddies SET srv_alias = :srvAlias " +
+                                   "WHERE id = :buddyId");
+          statement.params.buddyId = this.id;
+          statement.params.srvAlias = this._srvAlias;
+          statement.executeAsync();
+          this._notifyObservers("display-name-changed", aData);
+        }
+        break;
+      case "account-buddy-icon-changed":
+        if (this._isPreferredAccount(aSubject))
+          this._notifyObservers("icon-changed");
+        break;
+      case "account-buddy-added":
+        if (this._accounts.length == 0) {
+          // Add the new account in the empty buddy instance.
+          // The TagsById hack is to bypass the xpconnect wrapper.
+          this._addAccount(aSubject, TagsById[aSubject.tag.id]);
+          this._updateStatus();
+          this._notifyObservers("added");
+        }
+        else {
+          this._accounts.push(aSubject);
+          this.contact._moved(null, aSubject.tag);
+          this._updatePreferredAccount(aSubject);
+        }
+        break;
+      case "account-buddy-removed":
+        if (this._accounts.length == 1) {
+          let statement =
+            DBConn.createStatement("DELETE FROM buddies WHERE id = :id");
+          statement.params.id = this.id;
+          statement.execute();
+
+          this._notifyObservers("removed");
+
+          delete BuddiesById[this._id];
+          this.destroy();
+        }
+        else {
+          this._accounts = this._accounts.filter(function (ab) {
+            return (ab.account.numericId != aSubject.account.numericId ||
+                    ab.tag.id != aSubject.tag.id);
+          });
+          if (this._preferredAccount.account.numericId == aSubject.account.numericId &&
+              this._preferredAccount.tag.id == aSubject.tag.id) {
+            this._preferredAccount = null;
+            this._updatePreferredAccount();
+          }
+          this.contact._moved(aSubject.tag);
+        }
+        break;
+    }
+  },
+
+  getInterfaces: function(countRef) {
+    let interfaces = [Ci.nsIClassInfo, Ci.nsISupports, Ci.imIBuddy];
+    countRef.value = interfaces.length;
+    return interfaces;
+  },
+  getHelperForLanguage: function(language) null,
+  implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
+  flags: 0,
+  QueryInterface: XPCOMUtils.generateQI([Ci.imIBuddy, Ci.nsIClassInfo])
+};
+
+
+function ContactsService() { }
+ContactsService.prototype = {
+  initContacts: function() {
+    let statement = DBConn.createStatement("SELECT id, name FROM tags");
+    while (statement.executeStep())
+      Tags.push(new Tag(statement.getInt32(0), statement.getUTF8String(1)));
+
+    statement = DBConn.createStatement("SELECT id, alias FROM contacts");
+    while (statement.executeStep())
+      new Contact(statement.getInt32(0), statement.getUTF8String(1));
+
+    statement =
+      DBConn.createStatement("SELECT contact_id, tag_id FROM contact_tag");
+    while (statement.executeStep()) {
+      let contact = ContactsById[statement.getInt32(0)];
+      let tag = TagsById[statement.getInt32(1)];
+      contact._tags.push(tag);
+      tag._addContact(contact);
+    }
+
+    statement = DBConn.createStatement("SELECT id, key, name, srv_alias, contact_id FROM buddies ORDER BY position");
+    while (statement.executeStep())
+      new Buddy(statement.getInt32(0), statement.getUTF8String(1),
+                statement.getUTF8String(2), statement.getUTF8String(3),
+                statement.getInt32(4));
+    // FIXME is there a way to enforce that all AccountBuddies of a Buddy have the same protocol?
+
+    statement = DBConn.createStatement("SELECT account_id, buddy_id, tag_id FROM account_buddy");
+    while (statement.executeStep()) {
+      let accountId = statement.getInt32(0);
+      let buddyId = statement.getInt32(1);
+      let tagId = statement.getInt32(2);
+
+      if (!BuddiesById.hasOwnProperty(buddyId)) {
+        Cu.reportError("Corrupted database: account_buddy entry for account " +
+                       accountId + " and tag " + tagId +
+                       " references unknown buddy with id " + buddyId);
+        continue;
+      }
+
+      let buddy = BuddiesById[buddyId];
+      if (buddy._hasAccountBuddy(accountId, tagId)) {
+        Cu.reportError("Corrupted database: duplicated account_buddy entry: " +
+                       "account_id = " + accountId + ", buddy_id = " + buddyId +
+                       ", tag_id = " + tagId);
+        continue;
+      }
+
+      let account = Services.accounts.getAccountByNumericId(accountId);
+      let tag = TagsById[tagId];
+      try {
+        let accountBuddy = account.loadBuddy(buddy, tag);
+        if (accountBuddy)
+          buddy._addAccount(accountBuddy, tag);
+      } catch (e) {
+        // FIXME accountBuddy shouldn't be NULL (once imAccounts.js is finished)
+        // It currently doesn't work right with unknown protocols.
+        Components.utils.reportError(e);
+        dump(e + "\n");
+      }
+    }
+
+    otherContactsTag._initHiddenTags();
+  },
+  unInitContacts: function() {
+    Tags = [];
+    TagsById = { };
+    // Avoid shutdown leaks caused by references to native components
+    // implementing imIAccountBuddy.
+    for each (let buddy in BuddiesById)
+      buddy.destroy();
+    BuddiesById = { };
+    ContactsById = { };
+  },
+
+  getContactById: function(aId) ContactsById[aId],
+  getBuddyById: function(aId) BuddiesById[aId],
+  getBuddyByNameAndProtocol: function(aNormalizedName, aPrpl) {
+    let statement =
+      DBConn.createStatement("SELECT b.id FROM buddies b " +
+                             "JOIN account_buddy ab ON buddy_id = b.id " +
+                             "JOIN accounts a ON account_id = a.id " +
+                             "WHERE b.key = :buddyName and a.prpl = :prplId");
+    statement.params.buddyName = aNormalizedName;
+    statement.params.prplId = aPrpl.id;
+    if (!statement.executeStep())
+      return null;
+    return BuddiesById[statement.row.id];
+  },
+
+  accountBuddyAdded: function(aAccountBuddy) {
+    let account = aAccountBuddy.account;
+    let normalizedName = aAccountBuddy.normalizedName;
+    let buddy = this.getBuddyByNameAndProtocol(normalizedName, account.protocol);
+    if (!buddy) {
+      let statement =
+        DBConn.createStatement("INSERT INTO buddies " +
+                               "(key, name, srv_alias, position) " +
+                               "VALUES(:key, :name, :srvAlias, 0)");
+      let name = aAccountBuddy.userName;
+      let srvAlias = aAccountBuddy.serverAlias;
+      statement.params.key = normalizedName;
+      statement.params.name = name;
+      statement.params.srvAlias = srvAlias;
+      statement.execute();
+      buddy =
+        new Buddy(DBConn.lastInsertRowID, normalizedName, name, srvAlias, 0);
+    }
+
+    // Initialize the 'buddy' field of the imIAccountBuddy instance.
+    aAccountBuddy.buddy = buddy;
+
+    // Ensure we aren't storing a duplicate entry.
+    let accountId = account.numericId;
+    let tagId = aAccountBuddy.tag.id;
+    if (buddy._hasAccountBuddy(accountId, tagId)) {
+      Cu.reportError("Attempting to store a duplicate account buddy " +
+                     normalizedName + ", account id = " + accountId +
+                     ", tag id = " + tagId);
+      return;
+    }
+
+    // Store the new account buddy.
+    let statement =
+      DBConn.createStatement("INSERT INTO account_buddy " +
+                             "(account_id, buddy_id, tag_id) " +
+                             "VALUES(:accountId, :buddyId, :tagId)");
+    statement.params.accountId = accountId;
+    statement.params.buddyId = buddy.id;
+    statement.params.tagId = tagId;
+    statement.execute();
+
+    // Fire the notifications.
+    buddy.observe(aAccountBuddy, "account-buddy-added");
+  },
+  accountBuddyRemoved: function(aAccountBuddy) {
+    let buddy = aAccountBuddy.buddy;
+    let statement =
+      DBConn.createStatement("DELETE FROM account_buddy " +
+                                    "WHERE account_id = :accountId AND " +
+                                          "buddy_id = :buddyId AND " +
+                                          "tag_id = :tagId");
+    statement.params.accountId = aAccountBuddy.account.numericId;
+    statement.params.buddyId = buddy.id;
+    statement.params.tagId = aAccountBuddy.tag.id;
+    statement.execute();
+
+    buddy.observe(aAccountBuddy, "account-buddy-removed");
+  },
+
+  accountBuddyMoved: function(aAccountBuddy, aOldTag, aNewTag) {
+    let buddy = aAccountBuddy.buddy;
+    let statement =
+      DBConn.createStatement("UPDATE account_buddy " +
+                             "SET tag_id = :newTagId " +
+                             "WHERE account_id = :accountId AND " +
+                                   "buddy_id = :buddyId AND " +
+                                   "tag_id = :oldTagId");
+    statement.params.accountId = aAccountBuddy.account.numericId;
+    statement.params.buddyId = buddy.id;
+    statement.params.oldTagId = aOldTag.id;
+    statement.params.newTagId = aNewTag.id;
+    statement.execute();
+
+    buddy.observe(aAccountBuddy, "account-buddy-moved");
+    ContactsById[buddy.contact.id]._moved(aOldTag, aNewTag);
+  },
+
+  storeAccount: function(aId, aUserName, aPrplId) {
+    let statement =
+      DBConn.createStatement("SELECT name, prpl FROM accounts WHERE id = :id");
+    statement.params.id = aId;
+    if (statement.executeStep()) {
+      if (statement.getUTF8String(0) == aUserName &&
+          statement.getUTF8String(1) == aPrplId)
+        return; // The account is already stored correctly.
+      throw Cr.NS_ERROR_UNEXPECTED; // Corrupted database?!?
+    }
+
+    // Actually store the account.
+    statement = DBConn.createStatement("INSERT INTO accounts (id, name, prpl) " +
+                                       "VALUES(:id, :userName, :prplId)");
+    statement.params.id = aId;
+    statement.params.userName = aUserName;
+    statement.params.prplId = aPrplId;
+    statement.execute();
+  },
+  accountIdExists: function(aId) {
+    let statement =
+      DBConn.createStatement("SELECT id FROM accounts WHERE id = :id");
+    statement.params.id = aId;
+    return statement.executeStep();
+  },
+  forgetAccount: function(aId) {
+    let statement =
+      DBConn.createStatement("DELETE FROM accounts WHERE id = :accountId");
+    statement.params.accountId = aId;
+    statement.execute();
+
+    // removing the account from the accounts table is not enought,
+    // we need to remove all the associated account_buddy entries too
+    statement = DBConn.createStatement("DELETE FROM account_buddy " +
+                                       "WHERE account_id = :accountId");
+    statement.params.accountId = aId;
+    statement.execute();
+  },
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.imIContactsService]),
+  classDescription: "Contacts",
+  classID: Components.ID("{8c3725dd-ee26-489d-8135-736015af8c7f}"),
+  contractID: "@mozilla.org/chat/contacts-service;1"
+};
+
+const NSGetFactory = XPCOMUtils.generateNSGetFactory([ContactsService,
+                                                      TagsService]);
new file mode 100644
--- /dev/null
+++ b/chat/components/src/imContacts.manifest
@@ -0,0 +1,4 @@
+component {8c3725dd-ee26-489d-8135-736015af8c7f} imContacts.js
+contract @mozilla.org/chat/contacts-service;1 {8c3725dd-ee26-489d-8135-736015af8c7f}
+component {1fa92237-4303-4384-b8ac-4e65b50810a5} imContacts.js
+contract @mozilla.org/chat/tags-service;1 {1fa92237-4303-4384-b8ac-4e65b50810a5}
new file mode 100644
--- /dev/null
+++ b/chat/components/src/imConversations.js
@@ -0,0 +1,470 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2009.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+Cu.import("resource:///modules/imServices.jsm");
+Cu.import("resource:///modules/imStatusUtils.jsm");
+Cu.import("resource:///modules/imXPCOMUtils.jsm");
+Cu.import("resource:///modules/jsProtoHelper.jsm");
+
+var gLastUIConvId = 0;
+var gLastPurpleConvId = 0;
+
+XPCOMUtils.defineLazyGetter(this, "bundle", function()
+  Services.strings.createBundle("chrome://chat/locale/conversations.properties")
+);
+
+function UIConversation(aPurpleConversation)
+{
+  this._purpleConv = {};
+  this.id = ++gLastUIConvId;
+  this._observers = [];
+  this._messages = [];
+  this.changeTargetTo(aPurpleConversation);
+  let iface = Ci["prplIConv" + (aPurpleConversation.isChat ? "Chat" : "IM")];
+  this._interfaces = this._interfaces.concat(iface);
+  let contact = this.contact;
+  if (contact) {
+    // XPConnect will create a wrapper around 'this' here,
+    // so the list of exposed interfaces shouldn't change anymore.
+    contact.addObserver(this);
+    this._observedContact = contact;
+  }
+  Services.obs.notifyObservers(this, "new-ui-conversation", null);
+}
+
+UIConversation.prototype = {
+  __proto__: ClassInfo(["imIConversation", "prplIConversation", "nsIObserver"],
+                       "UI conversation"),
+  _observedContact: null,
+  get contact() {
+    let target = this.target;
+    if (!target.isChat && target.buddy)
+      return target.buddy.buddy.contact;
+    return null;
+  },
+  get target() this._purpleConv[this._currentTargetId],
+  set target(aPurpleConversation) {
+    this.changeTargetTo(aPurpleConversation);
+  },
+  _currentTargetId: 0,
+  changeTargetTo: function(aPurpleConversation) {
+    let id = aPurpleConversation.id;
+    if (this._currentTargetId == id)
+      return;
+
+    if (!(id in this._purpleConv)) {
+      this._purpleConv[id] = aPurpleConversation;
+      aPurpleConversation.addObserver(this.observeConv.bind(this, id));
+    }
+
+    let shouldNotify = this._currentTargetId;
+    this._currentTargetId = id;
+    if (!this.isChat) {
+      let buddy = this.buddy;
+      if (buddy)
+        ({statusType: this.statusType, statusText: this.statusText}) = buddy;
+    }
+    if (shouldNotify) {
+      this.notifyObservers(this, "target-purple-conversation-changed");
+      let target = this.target;
+      let params = [target.title, target.account.protocol.name];
+      this.systemMessage(bundle.formatStringFromName("targetChanged",
+                                                     params, params.length));
+    }
+  },
+  // Returns a boolean indicating if the ui-conversation was closed.
+  // If the conversation was closed, aContactId.value is set to the contact id
+  // or 0 if no contact was associated with the conversation.
+  removeTarget: function(aPurpleConversation, aContactId) {
+    let id = aPurpleConversation.id;
+    if (!(id in this._purpleConv))
+      throw "unknown purple conversation";
+
+    delete this._purpleConv[id];
+    if (this._currentTargetId != id)
+      return false;
+
+    for (let newId in this._purpleConv) {
+      this.changeTargetTo(this._purpleConv[newId]);
+      return false;
+    }
+
+    if (this._observedContact) {
+      this._observedContact.removeObserver(this);
+      aContactId.value = this._observedContact.id;
+      delete this._observedContact;
+    }
+    else
+      aContactId.value = 0;
+
+    delete this._currentTargetId;
+    this.notifyObservers(this, "ui-conversation-closed");
+    Services.obs.notifyObservers(this, "ui-conversation-closed", null);
+    return true;
+  },
+
+  _unreadMessageCount: 0,
+  get unreadMessageCount() this._unreadMessageCount,
+  _unreadTargetedMessageCount: 0,
+  get unreadTargetedMessageCount() this._unreadTargetedMessageCount,
+  _unreadIncomingMessageCount: 0,
+  get unreadIncomingMessageCount() this._unreadIncomingMessageCount,
+  markAsRead: function() {
+    delete this._unreadMessageCount;
+    delete this._unreadTargetedMessageCount;
+    delete this._unreadIncomingMessageCount;
+    this._notifyUnreadCountChanged();
+  },
+  _lastNotifiedUnreadCount: 0,
+  _notifyUnreadCountChanged: function() {
+    if (this._unreadIncomingMessageCount == this._lastNotifiedUnreadCount)
+      return;
+
+    this._lastNotifiedUnreadCount = this._unreadIncomingMessageCount;
+    for each (let observer in this._observers)
+      observer.observe(this, "unread-message-count-changed",
+                       this._unreadIncomingMessageCount.toString());
+  },
+  getMessages: function(aMessageCount) {
+    if (aMessageCount)
+      aMessageCount.value = this._messages.length;
+    return this._messages;
+  },
+  checkClose: function() {
+    if (!this._currentTargetId)
+      return true; // already closed.
+
+    if (!Services.prefs.getBoolPref("messenger.conversations.alwaysClose") &&
+        (this.isChat && !this.left ||
+         !this.isChat && this.unreadIncomingMessageCount != 0))
+      return false;
+
+    this.close();
+    return true;
+  },
+
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic == "contact-no-longer-dummy") {
+      let oldId = parseInt(aData);
+      // gConversationsService is ugly... :(
+      delete gConversationsService._uiConvByContactId[oldId];
+      gConversationsService._uiConvByContactId[aSubject.id] = this;
+    }
+    else if (aTopic == "account-buddy-status-changed") {
+      if (!this._statusUpdatePending &&
+          aSubject.account.id == this.account.id &&
+          aSubject.buddy.id == this.buddy.buddy.id) {
+        this._statusUpdatePending = true;
+        Services.tm.mainThread.dispatch(this.updateBuddyStatus.bind(this),
+                                        Ci.nsIEventTarget.DISPATCH_NORMAL);
+      }
+    }
+    else if (aTopic == "account-buddy-icon-changed") {
+      if (!this._statusUpdatePending &&
+          aSubject.account.id == this.account.id &&
+          aSubject.buddy.id == this.buddy.buddy.id) {
+        this._iconUpdatePending = true;
+        Services.tm.mainThread.dispatch(this.updateIcon.bind(this),
+                                        Ci.nsIEventTarget.DISPATCH_NORMAL);
+      }
+    }
+  },
+
+  _iconUpdatePending: false,
+  updateIcon: function() {
+    delete this._iconUpdatePending;
+    this.notifyObservers(this, "update-buddy-icon");
+  },
+
+  _statusUpdatePending: false,
+  updateBuddyStatus: function() {
+    delete this._statusUpdatePending;
+    let {statusType: statusType, statusText: statusText} = this.buddy;
+
+    if (("statusType" in this) && this.statusType == statusType &&
+        this.statusText == statusText)
+      return;
+
+    let wasUnknown = this.statusType == Ci.imIStatusInfo.STATUS_UNKNOWN;
+    this.statusType = statusType;
+    this.statusText = statusText;
+
+    this.notifyObservers(this, "update-buddy-status");
+
+    let msg;
+    if (statusType == Ci.imIStatusInfo.STATUS_UNKNOWN)
+      msg = bundle.formatStringFromName("statusUnknown", [this.title], 1);
+    else {
+      let status = Status.toLabel(statusType);
+      let stringId = wasUnknown ? "statusChangedFromUnknown" : "statusChanged";
+      if (statusText) {
+        msg = bundle.formatStringFromName(stringId + "WithStatusText",
+                                          [this.title, status, statusText],
+                                          3);
+      }
+      else
+        msg = bundle.formatStringFromName(stringId, [this.title, status], 2);
+    }
+    this.systemMessage(msg);
+  },
+
+  _disconnected: false,
+  disconnecting: function() {
+    if (this._disconnected)
+      return;
+
+    this._disconnected = true;
+    if (this.contact)
+      return; // handled by the contact observer.
+
+    this.systemMessage(bundle.GetStringFromName("accountDisconnected"));
+    this.notifyObservers(this, "update-buddy-status");
+  },
+  connected: function() {
+    delete this._disconnected;
+    this.notifyObservers(this, "update-buddy-status");
+  },
+
+  observeConv: function(aTargetId, aSubject, aTopic, aData) {
+    if (aTargetId != this._currentTargetId &&
+        (aTopic == "new-text" ||
+         (aTopic == "update-typing" &&
+          this._purpleConv[aTargetId].typingState == Ci.prplIConvIM.TYPING)))
+      this.target = this._purpleConv[aTargetId];
+    if (aTopic == "new-text")
+      Services.obs.notifyObservers(aSubject, aTopic, aData);
+    this.notifyObservers(aSubject, aTopic, aData);
+  },
+
+  systemMessage: function(aText, aIsError) {
+    let flags = {system: true, noLog: true, error: !!aIsError};
+    (new Message("system", aText, flags)).conversation = this;
+  },
+
+  // prplIConversation
+  get isChat() this.target.isChat,
+  get account() this.target.account,
+  get name() this.target.name,
+  get normalizedName() this.target.normalizedName,
+  get title() this.target.title,
+  sendMsg: function (aMsg) { this.target.sendMsg(aMsg); },
+  unInit: function() {
+    for each (let conv in this._purpleConv)
+      gConversationsService.forgetConversation(conv);
+    if (this._observedContact) {
+      this._observedContact.removeObserver(this);
+      delete this._observedContact;
+    }
+    this._purpleConv = {}; // Prevent .close from failing.
+    delete this._currentTargetId;
+  },
+  close: function() {
+    for each (let conv in this._purpleConv)
+      conv.close();
+    if (!this.hasOwnProperty("_currentTargetId"))
+      return;
+    delete this._currentTargetId;
+    this.notifyObservers(this, "ui-conversation-closed");
+    Services.obs.notifyObservers(this, "ui-conversation-closed", null);
+  },
+  addObserver: function(aObserver) {
+    if (this._observers.indexOf(aObserver) == -1)
+      this._observers.push(aObserver);
+  },
+  removeObserver: function(aObserver) {
+    this._observers = this._observers.filter(function(o) o !== aObserver);
+  },
+  notifyObservers: function(aSubject, aTopic, aData) {
+    if (aTopic == "new-text") {
+      this._messages.push(aSubject);
+      ++this._unreadMessageCount;
+      if (aSubject.incoming && !aSubject.system) {
+        ++this._unreadIncomingMessageCount;
+        if (!this.isChat || aSubject.containsNick)
+          ++this._unreadTargetedMessageCount;
+      }
+    }
+    for each (let observer in this._observers) {
+      if (!observer.observe && this._observers.indexOf(observer) == -1)
+        continue; // observer removed by a previous call to another observer.
+      observer.observe(aSubject, aTopic, aData);
+    }
+    this._notifyUnreadCountChanged();
+  },
+
+  // prplIConvIM
+  get buddy() this.target.buddy,
+  get typingState() this.target.typingState,
+  sendTyping: function(aLength) { this.target.sendTyping(aLength); },
+
+  // Chat only
+  getParticipants: function() this.target.getParticipants(),
+  get topic() this.target.topic,
+  set topic(aTopic) { this.target.topic = aTopic; },
+  get topicSetter() this.target.topicSetter,
+  get topicSettable() this.target.topicSettable,
+  get nick() this.target.nick,
+  get left() this.target.left
+};
+
+var gConversationsService;
+function ConversationsService() { gConversationsService = this; }
+ConversationsService.prototype = {
+  get wrappedJSObject() this,
+
+  initConversations: function() {
+    this._uiConv = {};
+    this._uiConvByContactId = {};
+    this._purpleConversations = [];
+    Services.obs.addObserver(this, "account-disconnecting", false);
+    Services.obs.addObserver(this, "account-connected", false);
+  },
+
+  unInitConversations: function() {
+    for each (let UIConv in this._uiConv)
+      UIConv.unInit();
+    delete this._uiConv;
+    delete this._uiConvByContactId;
+    // This should already be empty, but just to be sure...
+    for each (let purpleConv in this._purpleConversations)
+      purpleConv.unInit();
+    delete this._purpleConversations;
+    Services.obs.removeObserver(this, "account-disconnecting");
+    Services.obs.removeObserver(this, "account-connected");
+  },
+
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic == "account-connected") {
+      for each (let conv in this._uiConv) {
+        if (conv.account.id == aSubject.id)
+          conv.connected();
+      }
+    }
+    else if (aTopic == "account-disconnecting") {
+      for each (let conv in this._uiConv) {
+        if (conv.account.id == aSubject.id)
+          conv.disconnecting();
+      }
+    }
+  },
+
+  addConversation: function(aPurpleConversation) {
+    // Give an id to the new conversation.
+    aPurpleConversation.id = ++gLastPurpleConvId;
+    this._purpleConversations.push(aPurpleConversation);
+
+    // Notify observers.
+    Services.obs.notifyObservers(aPurpleConversation, "new-conversation", null);
+
+    // Update or create the corresponding UI conversation.
+    let contactId;
+    if (!aPurpleConversation.isChat) {
+      let accountBuddy = aPurpleConversation.buddy;
+      if (accountBuddy)
+        contactId = accountBuddy.buddy.contact.id;
+    }
+
+    if (contactId) {
+      if (contactId in this._uiConvByContactId) {
+        let uiConv = this._uiConvByContactId[contactId];
+        uiConv.target = aPurpleConversation;
+        this._uiConv[aPurpleConversation.id] = uiConv;
+        return;
+      }
+    }
+
+    let newUIConv = new UIConversation(aPurpleConversation);
+    this._uiConv[aPurpleConversation.id] = newUIConv;
+    if (contactId)
+      this._uiConvByContactId[contactId] = newUIConv;
+  },
+  removeConversation: function(aPurpleConversation) {
+    Services.obs.notifyObservers(aPurpleConversation, "conversation-closed", null);
+
+    let uiConv = this.getUIConversation(aPurpleConversation);
+    let contactId = {};
+    if (uiConv.removeTarget(aPurpleConversation, contactId)) {
+      delete this._uiConv[aPurpleConversation.id];
+      if (contactId.value)
+        delete this._uiConvByContactId[contactId.value];
+    }
+    this.forgetConversation(aPurpleConversation);
+  },
+  forgetConversation: function(aPurpleConversation) {
+    aPurpleConversation.unInit();
+
+    this._purpleConversations =
+      this._purpleConversations.filter(function(c) c !== aPurpleConversation);
+  },
+
+  getUIConversations: function(aConvCount) {
+    let rv = Object.keys(this._uiConv).map(function (k) this._uiConv[k], this);
+    aConvCount.value = rv.length;
+    return rv;
+  },
+  getUIConversation: function(aPurpleConversation) {
+    let id = aPurpleConversation.id;
+    if (id in this._uiConv)
+      return this._uiConv[id];
+    throw "Unknown conversation";
+  },
+  getUIConversationByContactId: function(aId)
+    (aId in this._uiConvByContactId) ? this._uiConvByContactId[aId] : null,
+
+  getConversations: function() new nsSimpleEnumerator(this._purpleConversations),
+  getConversationById: function(aId) {
+    for each (let conv in this._purpleConversations)
+      if (conv.id == aId)
+        return conv;
+    return null;
+  },
+  getConversationByNameAndAccount: function(aName, aAccount, aIsChat) {
+    for each (let conv in this._purpleConversations)
+      if (conv.name == aName && conv.account.numericId == aAccount.numericId &&
+          conv.isChat == aIsChat)
+        return conv;
+    return null;
+  },
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.imIConversationsService]),
+  classDescription: "Conversations",
+  classID: Components.ID("{b2397cd5-c76d-4618-8410-f344c7c6443a}"),
+  contractID: "@mozilla.org/chat/conversations-service;1"
+};
+
+const NSGetFactory = XPCOMUtils.generateNSGetFactory([ConversationsService]);
new file mode 100644
--- /dev/null
+++ b/chat/components/src/imConversations.manifest
@@ -0,0 +1,2 @@
+component {b2397cd5-c76d-4618-8410-f344c7c6443a} imConversations.js
+contract @mozilla.org/chat/conversations-service;1 {b2397cd5-c76d-4618-8410-f344c7c6443a}
new file mode 100644
--- /dev/null
+++ b/chat/components/src/imCore.js
@@ -0,0 +1,384 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2011.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+Cu.import("resource:///modules/imServices.jsm");
+Cu.import("resource:///modules/imXPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "categoryManager",
+                                   "@mozilla.org/categorymanager;1",
+                                   "nsICategoryManager");
+
+const kQuitApplicationGranted = "quit-application-granted";
+const kProtocolPluginCategory = "im-protocol-plugin";
+
+const kPrefReportIdle =        "messenger.status.reportIdle";
+const kPrefUserIconFilename =  "messenger.status.userIconFileName";
+const kPrefUserDisplayname =   "messenger.status.userDisplayName";
+const kPrefTimeBeforeIdle =    "messenger.status.timeBeforeIdle";
+const kPrefAwayWhenIdle =      "messenger.status.awayWhenIdle";
+const kPrefDefaultMessage =    "messenger.status.defaultIdleAwayMessage";
+
+const NS_IOSERVICE_GOING_OFFLINE_TOPIC = "network:offline-about-to-go-offline";
+const NS_IOSERVICE_OFFLINE_STATUS_TOPIC = "network:offline-status-changed";
+
+function UserStatus()
+{
+  this._observers = [];
+
+  if (Services.prefs.getBoolPref(kPrefReportIdle))
+    this._addIdleObserver();
+  Services.prefs.addObserver(kPrefReportIdle, this, false);
+
+  if (Services.io.offline)
+    this._offlineStatusType = Ci.imIStatusInfo.STATUS_OFFLINE;
+  Services.obs.addObserver(this, NS_IOSERVICE_GOING_OFFLINE_TOPIC, false);
+  Services.obs.addObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC, false);
+}
+UserStatus.prototype = {
+  __proto__: ClassInfo("imIUserStatusInfo", "User status info"),
+
+  unInit: function() {
+    this._observers = [];
+    Services.prefs.removeObserver(kPrefReportIdle, this);
+    if (this._observingIdleness)
+      this._removeIdleObserver();
+    Services.obs.removeObserver(this, NS_IOSERVICE_GOING_OFFLINE_TOPIC);
+    Services.obs.removeObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC);
+  },
+  _observingIdleness: false,
+  _addIdleObserver: function() {
+    this._observingIdleness = true;
+    this._idleService =
+      Cc["@mozilla.org/widget/idleservice;1"].getService(Ci.nsIIdleService);
+    Services.obs.addObserver(this, "im-sent", false);
+
+    this._timeBeforeIdle = Services.prefs.getIntPref(kPrefTimeBeforeIdle);
+    if (this._timeBeforeIdle < 0)
+      this._timeBeforeIdle = 0;
+    Services.prefs.addObserver(kPrefTimeBeforeIdle, this, false);
+    if (this._timeBeforeIdle)
+      this._idleService.addIdleObserver(this, this._timeBeforeIdle);
+  },
+  _removeIdleObserver: function() {
+    if (this._timeBeforeIdle)
+      this._idleService.removeIdleObserver(this, this._timeBeforeIdle);
+
+    Services.prefs.removeObserver(kPrefTimeBeforeIdle, this);
+    delete this._timeBeforeIdle;
+
+    Services.obs.removeObserver(this, "im-sent");
+    delete this._idleService;
+    delete this._observingIdleness;
+  },
+
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic == "nsPref:changed") {
+      if (aData == kPrefReportIdle) {
+        let reportIdle = Services.prefs.getBoolPref(kPrefReportIdle);
+        if (reportIdle && !this._observingIdleness)
+          this._addIdleObserver();
+        else if (!reportIdle && this._observingIdleness)
+        this._removeIdleObserver();
+      }
+      else if (aData == kPrefTimeBeforeIdle) {
+        let timeBeforeIdle = Services.prefs.getIntPref(kPrefTimeBeforeIdle);
+        if (timeBeforeIdle != this._timeBeforeIdle) {
+          if (this._timeBeforeIdle)
+            this._idleService.removeIdleObserver(this, this._timeBeforeIdle);
+          this._timeBeforeIdle = timeBeforeIdle;
+          if (this._timeBeforeIdle)
+            this._idleService.addIdleObserver(this, this._timeBeforeIdle);
+        }
+      }
+      else
+        throw Cr.NS_ERROR_UNEXPECTED;
+    }
+    else if (aTopic == NS_IOSERVICE_GOING_OFFLINE_TOPIC)
+      this.offline = true;
+    else if (aTopic == NS_IOSERVICE_OFFLINE_STATUS_TOPIC && aData == "online")
+      this.offline = false;
+    else
+      this._checkIdle();
+  },
+
+  _offlineStatusType: Ci.imIStatusInfo.STATUS_AVAILABLE,
+  set offline(aOffline) {
+    let statusType = this.statusType;
+    let statusText = this.statusText;
+    if (aOffline)
+      this._offlineStatusType = Ci.imIStatusInfo.STATUS_OFFLINE;
+    else
+      delete this._offlineStatusType;
+    if (this.statusType != statusType || this.statusText != statusText)
+      this._notifyObservers("status-changed", this.statusText);
+  },
+
+  _idleTime: 0,
+  get idleTime() this._idleTime,
+  set idleTime(aIdleTime) {
+    this._idleTime = aIdleTime;
+    this._notifyObservers("idle-time-changed", aIdleTime);
+  },
+  _idle: false,
+  _idleStatusText: "",
+  _idleStatusType: Ci.imIStatusInfo.STATUS_AVAILABLE,
+  _checkIdle: function() {
+    let idleTime = Math.floor(this._idleService.idleTime / 1000);
+    let idle = this._timeBeforeIdle && idleTime >= this._timeBeforeIdle;
+    if (idle == this._idle)
+      return;
+
+    let statusType = this.statusType;
+    let statusText = this.statusText;
+    this._idle = idle;
+    if (idle) {
+      this.idleTime = idleTime;
+      if (Services.prefs.getBoolPref(kPrefAwayWhenIdle)) {
+        this._idleStatusType = Ci.imIStatusInfo.STATUS_AWAY;
+        this._idleStatusText =
+          Services.prefs.getComplexValue(kPrefDefaultMessage,
+                                         Ci.nsIPrefLocalizedString).data;
+      }
+    }
+    else {
+      this.idleTime = 0;
+      delete this._idleStatusType;
+      delete this._idleStatusText;
+    }
+    if (this.statusType != statusType || this.statusText != statusText)
+      this._notifyObservers("status-changed", this.statusText);
+  },
+
+  _statusText: "",
+  get statusText() this._statusText || this._idleStatusText,
+  _statusType: Ci.imIStatusInfo.STATUS_AVAILABLE,
+  get statusType() Math.min(this._statusType, this._idleStatusType, this._offlineStatusType),
+  setStatus: function(aStatus, aMessage) {
+    if (aStatus != Ci.imIStatusInfo.STATUS_UNKNOWN)
+      this._statusType = aStatus;
+    if (aStatus != Ci.imIStatusInfo.STATUS_OFFLINE)
+      this._statusText = aMessage;
+    this._notifyObservers("status-changed", aMessage);
+  },
+
+  _getProfileDir: function()
+    Services.dirsvc.get("ProfD", Ci.nsIFile),
+  setUserIcon: function(aIconFile) {
+    let folder = this._getProfileDir();
+
+    let newName = "";
+    if (aIconFile) {
+      // Get the extension (remove trailing dots - invalid Windows extension).
+      let ext = aIconFile.leafName.replace(/.*(\.[a-z0-9]+)\.*/i, "$1");
+      // newName = userIcon-<timestamp(now)>.<aIconFile.extension>
+      newName = "userIcon-" + Math.floor(Date.now() / 1000) + ext;
+
+      // Copy the new icon file to newName in the profile folder.
+      aIconFile.copyTo(folder, newName);
+    }
+
+    // Get the previous file name before saving the new file name.
+    let oldFileName = Services.prefs.getCharPref(kPrefUserIconFilename);
+    Services.prefs.setCharPref(kPrefUserIconFilename, newName);
+
+    // Now that the new icon has been copied to the profile directory
+    // and the pref value changed, we can remove the old icon. Ignore
+    // failures so that we always fire the user-icon-changed notification.
+    try {
+      if (oldFileName) {
+        folder.append(oldFileName);
+        if (folder.exists())
+          folder.remove(false);
+      }
+    } catch (e) {
+      Cu.reportError(e);
+    }
+
+    this._notifyObservers("user-icon-changed", newName);
+  },
+  getUserIcon: function() {
+    let filename = Services.prefs.getCharPref(kPrefUserIconFilename);
+    if (!filename)
+      return null; // No icon has been set.
+
+    let file = this._getProfileDir();
+    file.append(filename);
+
+    if (!file.exists()) {
+      Services.console.logStringMessage("Invalid userIconFileName preference");
+      return null;
+    }
+
+    return Services.io.newFileURI(file);
+  },
+
+  get displayName() Services.prefs.getComplexValue(kPrefUserDisplayname,
+                                                   Ci.nsISupportsString).data,
+  set displayName(aDisplayName) {
+    let str = Cc["@mozilla.org/supports-string;1"]
+              .createInstance(Ci.nsISupportsString);
+    str.data = aDisplayName;
+    Services.prefs.setComplexValue(kPrefUserDisplayname, Ci.nsISupportsString,
+                                   str);
+    this._notifyObservers("user-display-name-changed", aDisplayName);
+  },
+
+  addObserver: function(aObserver) {
+    if (this._observers.indexOf(aObserver) == -1)
+      this._observers.push(aObserver);
+  },
+  removeObserver: function(aObserver) {
+    this._observers = this._observers.filter(function(o) o !== aObserver);
+  },
+  _notifyObservers: function(aTopic, aData) {
+    for each (let observer in this._observers)
+      observer.observe(this, aTopic, aData);
+  }
+};
+
+var gCoreService;
+function CoreService() { gCoreService = this; }
+CoreService.prototype = {
+  _initialized: false,
+  globalUserStatus: null,
+  init: function() {
+    if (this._initialized)
+      return;
+
+    Services.obs.addObserver(this, kQuitApplicationGranted, false);
+    this._initialized = true;
+
+    Services.cmd.initCommands();
+    this._protos = {};
+
+    this.globalUserStatus = new UserStatus();
+    this.globalUserStatus.addObserver({
+      observe: function(aSubject, aTopic, aData) {
+        Services.obs.notifyObservers(aSubject, aTopic, aData);
+      }
+    });
+
+    let accounts = Services.accounts;
+    accounts.initAccounts();
+    Services.contacts.initContacts();
+    Services.conversations.initConversations();
+
+    if (accounts.autoLoginStatus == Ci.imIAccountsService.AUTOLOGIN_ENABLED)
+      accounts.processAutoLogin();
+  },
+  observe: function(aObject, aTopic, aData) {
+    if (aTopic == kQuitApplicationGranted)
+      this.quit();
+  },
+  quit: function() {
+    if (!this._initialized)
+      throw Cr.NS_ERROR_NOT_INITIALIZED;
+
+    Services.obs.removeObserver(this, kQuitApplicationGranted);
+    Services.obs.notifyObservers(this, "prpl-quit", null);
+
+    Services.conversations.unInitConversations();
+    Services.accounts.unInitAccounts();
+    Services.contacts.unInitContacts();
+    Services.cmd.unInitCommands();
+
+    this.globalUserStatus.unInit();
+    delete this.globalUserStatus;
+    delete this._protos;
+    delete this._initialized;
+  },
+
+  getProtocols: function() {
+    if (!this._initialized)
+      throw Cr.NS_ERROR_NOT_INITIALIZED;
+
+    let protocols = [];
+    let entries = categoryManager.enumerateCategory(kProtocolPluginCategory);
+    while (entries.hasMoreElements()) {
+      let id = entries.getNext().QueryInterface(Ci.nsISupportsCString).data;
+      let proto = this.getProtocolById(id);
+      if (proto)
+        protocols.push(proto);
+    }
+    return new nsSimpleEnumerator(protocols);
+  },
+
+  getProtocolById: function(aPrplId) {
+    if (!this._initialized)
+      throw Cr.NS_ERROR_NOT_INITIALIZED;
+
+    if (this._protos.hasOwnProperty(aPrplId))
+      return this._protos[aPrplId];
+
+    let cid;
+    try {
+      cid = categoryManager.getCategoryEntry(kProtocolPluginCategory, aPrplId);
+    } catch (e) {
+      return null; // no protocol registered for this id.
+    }
+
+    let proto = null;
+    try {
+      proto = Cc[cid].createInstance(Ci.prplIProtocol);
+    } catch (e) {
+      // This is a real error, the protocol is registered and failed to init.
+      let error = "failed to create an instance of " + cid + ": " + e;
+      dump(error + "\n");
+      Cu.reportError(error);
+    }
+    if (!proto)
+      return null;
+
+    try {
+      proto.init(aPrplId);
+    } catch (e) {
+      Cu.reportError(e);
+      return null;
+    }
+
+    this._protos[aPrplId] = proto;
+    return proto;
+  },
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.imICoreService]),
+  classDescription: "Core",
+  classID: Components.ID("{073f5953-853c-4a38-bd81-255510c31c2e}"),
+  contractID: "@mozilla.org/chat/core-service;1"
+};
+
+const NSGetFactory = XPCOMUtils.generateNSGetFactory([CoreService]);
new file mode 100644
--- /dev/null
+++ b/chat/components/src/imCore.manifest
@@ -0,0 +1,2 @@
+component {073f5953-853c-4a38-bd81-255510c31c2e} imCore.js
+contract @mozilla.org/chat/core-service;1 {073f5953-853c-4a38-bd81-255510c31c2e}
new file mode 100644
--- /dev/null
+++ b/chat/components/src/logger.js
@@ -0,0 +1,527 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2010.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const {classes: Cc, interfaces: Ci, utils: Cu, Constructor: CC} = Components;
+
+Cu.import("resource:///modules/hiddenWindow.jsm");
+Cu.import("resource:///modules/imServices.jsm");
+Cu.import("resource:///modules/imXPCOMUtils.jsm");
+Cu.import("resource:///modules/jsProtoHelper.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "logDir", function() {
+  let file = Services.dirsvc.get("ProfD", Ci.nsIFile);
+  file.append("logs");
+  return file;
+});
+
+const FileInputStream = CC("@mozilla.org/network/file-input-stream;1",
+                           "nsIFileInputStream",
+                           "init");
+const ConverterInputStream = CC("@mozilla.org/intl/converter-input-stream;1",
+                                "nsIConverterInputStream",
+                                "init");
+const LocalFile = CC("@mozilla.org/file/local;1",
+                     "nsILocalFile",
+                     "initWithPath");
+
+const kLineBreak = "@mozilla.org/windows-registry-key;1" in Cc ? "\r\n" : "\n";
+
+function getLogFolderForAccount(aAccount, aCreate)
+{
+  let file = logDir.clone();
+  function createIfNotExists(aFile) {
+    if (aCreate && !aFile.exists())
+      aFile.create(Ci.nsIFile.DIRECTORY_TYPE, 0777);
+  }
+  createIfNotExists(file);
+  file.append(aAccount.protocol.normalizedName);
+  createIfNotExists(file);
+  file.append(aAccount.normalizedName);
+  createIfNotExists(file);
+  return file;
+}
+
+function getNewLogFileName(aFormat)
+{
+  let date = new Date();
+  let dateTime = date.toLocaleFormat("%Y-%m-%d.%H%M%S");
+  let offset = date.getTimezoneOffset();
+  if (offset < 0) {
+    dateTime += "+";
+    offset *= -1;
+  }
+  else
+    dateTime += "-";
+  let minutes = offset % 60;
+  offset = (offset - minutes) / 60;
+  function twoDigits(aNumber)
+    aNumber == 0 ? "00" : aNumber < 10 ? "0" + aNumber : aNumber;
+  if (!aFormat)
+    aFormat = "txt";
+  return dateTime + twoDigits(offset) + twoDigits(minutes) + "." + aFormat;
+}
+
+/* Conversation logs stuff */
+function ConversationLog(aConversation)
+{
+  this._conv = aConversation;
+}
+ConversationLog.prototype = {
+  _log: null,
+  format: "txt",
+  _init: function cl_init() {
+    let file = getLogFolderForAccount(this._conv.account, true);
+    let name = this._conv.normalizedName;
+    if (this._conv.isChat && this._conv.account.protocol.id != "prpl-twitter")
+      name += ".chat";
+    file.append(name);
+    if (!file.exists())
+      file.create(Ci.nsIFile.DIRECTORY_TYPE, 0777);
+    if (Services.prefs.getCharPref("purple.logging.format") == "json")
+      this.format = "json";
+    file.append(getNewLogFileName(this.format));
+    let os = Cc["@mozilla.org/network/file-output-stream;1"].
+             createInstance(Ci.nsIFileOutputStream);
+    const PR_WRITE_ONLY   = 0x02;
+    const PR_CREATE_FILE  = 0x08;
+    const PR_APPEND       = 0x10;
+    os.init(file, PR_WRITE_ONLY | PR_CREATE_FILE | PR_APPEND, 0666, 0);
+    // just to be really sure everything is in UTF8
+    let converter = Cc["@mozilla.org/intl/converter-output-stream;1"].
+                    createInstance(Ci.nsIConverterOutputStream);
+    converter.init(os, "UTF-8", 0, 0);
+    this._log = converter;
+    this._log.writeString(this._getHeader());
+  },
+  _getHeader: function cl_getHeader()
+  {
+    let account = this._conv.account;
+    if (this.format == "json") {
+      return JSON.stringify({date: new Date(),
+                             name: this._conv.name,
+                             title: this._conv.title,
+                             account: account.normalizedName,
+                             protocol: account.protocol.normalizedName
+                            }) + "\n";
+    }
+    return "Conversation with " + this._conv.name +
+           " at " + (new Date).toLocaleString() +
+           " on " + account.name +
+           " (" + account.protocol.normalizedName + ")" + kLineBreak;
+  },
+  _serialize: function cl_serialize(aString) {
+    // TODO cleanup once bug 102699 is fixed
+    let doc = getHiddenHTMLWindow().document;
+    let div = doc.createElementNS("http://www.w3.org/1999/xhtml", "div");
+    div.innerHTML = aString.replace(/\r?\n/g, "<br/>").replace(/<br>/gi, "<br/>");
+    const type = "text/plain";
+    let encoder =
+      Components.classes["@mozilla.org/layout/documentEncoder;1?type=" + type]
+                .createInstance(Components.interfaces.nsIDocumentEncoder);
+    encoder.init(doc, type, 0);
+    encoder.setContainerNode(div);
+    encoder.setNodeFixup({fixupNode: function(aNode, aSerializeKids) {
+      if (aNode.localName == "a" && aNode.hasAttribute("href")) {
+        let url = aNode.getAttribute("href");
+        let content = aNode.textContent;
+        if (url != content)
+          aNode.textContent = content + " (" + url + ")";
+      }
+      return null;
+    }});
+    return encoder.encodeToString();
+  },
+  logMessage: function cl_logMessage(aMessage) {
+    if (!this._log)
+      this._init();
+
+    if (this.format == "json") {
+      let msg = {
+        date: new Date(aMessage.time * 1000),
+        who: aMessage.who,
+        text: aMessage.originalMessage,
+        flags: ["outgoing", "incoming", "system", "autoResponse",
+                "containsNick", "error", "delayed",
+                "noFormat", "containsImages", "notification",
+                "noLinkification"].filter(function(f) aMessage[f])
+      };
+      let alias = aMessage.alias;
+      if (alias && alias != msg.who)
+        msg.alias = alias;
+      this._log.writeString(JSON.stringify(msg) + "\n");
+      return;
+    }
+
+    let date = new Date(aMessage.time * 1000);
+    let line = "(" + date.toLocaleTimeString() + ") ";
+    let msg = this._serialize(aMessage.originalMessage);
+    if (aMessage.system)
+      line += msg;
+    else {
+      let sender = aMessage.alias || aMessage.who;
+      if (aMessage.autoResponse)
+        line += sender + " <AUTO-REPLY>: " + msg;
+      else {
+        if (/^\/me /.test(msg))
+          line += "***" + sender + " " + msg.replace(/^\/me /, "");
+        else
+          line += sender + ": " + msg;
+      }
+    }
+    this._log.writeString(line + kLineBreak);
+  },
+
+  close: function cl_close() {
+    if (this._log) {
+      this._log.close();
+      this._log = null;
+    }
+  }
+};
+
+const dummyConversationLog = {
+  logMessage: function() {},
+  close: function() {}
+};
+
+var gConversationLogs = { };
+function getLogForConversation(aConversation)
+{
+  let id = aConversation.id;
+  if (!(id in gConversationLogs)) {
+    let prefName =
+      "purple.logging.log_" + (aConversation.isChat ? "chats" : "ims");
+    if (Services.prefs.getBoolPref(prefName))
+      gConversationLogs[id] = new ConversationLog(aConversation);
+    else
+      gConversationLogs[id] = dummyConversationLog;
+  }
+  return gConversationLogs[id];
+}
+
+function closeLogForConversation(aConversation)
+{
+  let id = aConversation.id;
+  if (!(id in gConversationLogs))
+    return;
+  gConversationLogs[id].close();
+  delete gConversationLogs[id];
+}
+
+/* System logs stuff */
+function SystemLog(aAccount)
+{
+  this._init(aAccount);
+  this._log.writeString("System log for account " + aAccount.name +
+                        " (" + aAccount.protocol.normalizedName +
+                        ") connected at " +
+                        (new Date()).toLocaleFormat("%c") + kLineBreak);
+}
+SystemLog.prototype = {
+  _log: null,
+  _init: function sl_init(aAccount) {
+    let file = getLogFolderForAccount(aAccount, true);
+    file.append(".system");
+    if (!file.exists())
+      file.create(Ci.nsIFile.DIRECTORY_TYPE, 0777);
+    file.append(getNewLogFileName());
+    let os = Cc["@mozilla.org/network/file-output-stream;1"].
+             createInstance(Ci.nsIFileOutputStream);
+    const PR_WRITE_ONLY   = 0x02;
+    const PR_CREATE_FILE  = 0x08;
+    const PR_APPEND       = 0x10;
+    os.init(file, PR_WRITE_ONLY | PR_CREATE_FILE | PR_APPEND, 0666, 0);
+    // just to be really sure everything is in UTF8
+    let converter = Cc["@mozilla.org/intl/converter-output-stream;1"].
+                    createInstance(Ci.nsIConverterOutputStream);
+    converter.init(os, "UTF-8", 0, 0);
+    this._log = converter;
+  },
+  logEvent: function sl_logEvent(aString) {
+    if (!this._log)
+      this._init();
+
+    let date = (new Date()).toLocaleFormat("%x %X");
+    this._log.writeString("---- " + aString + " @ " + date + " ----" + kLineBreak);
+  },
+
+  close: function sl_close() {
+    if (this._log) {
+      this._log.close();
+      this._log = null;
+    }
+  }
+};
+
+const dummySystemLog = {
+  logEvent: function(aString) {},
+  close: function() {}
+};
+
+var gSystemLogs = { };
+function getLogForAccount(aAccount, aCreate)
+{
+  let id = aAccount.id;
+  if (aCreate) {
+    if (id in gSystemLogs)
+      gSystemLogs[id].close();
+    if (!Services.prefs.getBoolPref("purple.logging.log_system"))
+      return dummySystemLog;
+    return (gSystemLogs[id] = new SystemLog(aAccount));
+  }
+
+  return (id in gSystemLogs) && gSystemLogs[id] || dummySystemLog;
+}
+
+function closeLogForAccount(aAccount)
+{
+  let id = aAccount.id;
+  if (!(id in gSystemLogs))
+    return;
+  gSystemLogs[id].close();
+  delete gSystemLogs[id];
+}
+
+function LogMessage(aData, aConversation)
+{
+  this._init(aData.who, aData.text);
+  this._conversation = aConversation;
+  this.time = Math.round(new Date(aData.date) / 1000);
+  if ("alias" in aData)
+    this._alias = aData.alias;
+  for each (let flag in aData.flags)
+    this[flag] = true;
+}
+LogMessage.prototype = GenericMessagePrototype;
+
+function LogConversation(aLineInputStream)
+{
+  let line = {value: ""};
+  let more = aLineInputStream.readLine(line);
+
+  if (!line.value)
+    throw "bad log file";
+
+  let data = JSON.parse(line.value);
+  this.name = data.name;
+  this.title = data.title;
+  this._accountName = data.account;
+  this._protocolName = data.protocol;
+
+  this._messages = [];
+  while (more) {
+    more = aLineInputStream.readLine(line);
+    if (!line.value)
+      break;
+    let data = JSON.parse(line.value);
+    this._messages.push(new LogMessage(data, this));
+  }
+}
+LogConversation.prototype = {
+  __proto__: ClassInfo("imILogConversation", "Log conversation object"),
+  get isChat() false,
+  get buddy() null,
+  get account() ({
+    alias: "",
+    name: this._accountName,
+    normalizedName: this._accountName,
+    protocol: {name: this._protocolName},
+    statusInfo: Services.core.globalUserStatus
+  }),
+  getMessages: function(aMessageCount) {
+    if (aMessageCount)
+      aMessageCount.value = this._messages.length;
+    return this._messages;
+  }
+};
+
+/* Generic log enumeration stuff */
+function Log(aFile)
+{
+  this.file = aFile;
+  this.path = aFile.path;
+  const regexp = /([0-9]{4})-([0-9]{2})-([0-9]{2}).([0-9]{2})([0-9]{2})([0-9]{2})([+-])([0-9]{2})([0-9]{2}).*\.([a-z]+)$/;
+  let r = aFile.leafName.match(regexp);
+  let date = new Date(r[1], r[2] - 1, r[3], r[4], r[5], r[6]);
+  let offset = r[7] * 60 + r[8];
+  if (r[6] == -1)
+    offset *= -1;
+  this.time = date.valueOf() / 1000; // ignore the timezone offset for now (FIXME)
+  this.format = r[10];
+}
+Log.prototype = {
+  __proto__: ClassInfo("imILog", "Log object"),
+  getConversation: function() {
+    if (this.format != "json")
+      return null;
+
+    const PR_RDONLY = 0x01;
+    let fis = new FileInputStream(this.file, PR_RDONLY, 0444,
+                                  Ci.nsIFileInputStream.CLOSE_ON_EOF);
+    let lis = new ConverterInputStream(fis, "UTF-8", 1024, 0x0);
+    lis.QueryInterface(Ci.nsIUnicharLineInputStream);
+    return new LogConversation(lis);
+  }
+};
+
+function LogEnumerator(aEntries)
+{
+  this._entries = aEntries;
+}
+LogEnumerator.prototype = {
+  _entries: [],
+  hasMoreElements: function() {
+    while (this._entries.length > 0 && !this._entries[0].hasMoreElements())
+      this._entries.shift();
+    return this._entries.length > 0;
+  },
+  getNext: function()
+    new Log(this._entries[0].getNext().QueryInterface(Ci.nsIFile)),
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsISimpleEnumerator])
+};
+
+function Logger() { }
+Logger.prototype = {
+  _enumerateLogs: function logger__enumerateLogs(aAccount, aNormalizedName) {
+    let file = getLogFolderForAccount(aAccount);
+    file.append(aNormalizedName);
+    if (!file.exists())
+      return EmptyEnumerator;
+
+    return new LogEnumerator([file.directoryEntries]);
+  },
+  getLogFromFile: function logger_getLogFromFile(aFile) new Log(aFile),
+  getLogsForContact: function logger_getLogsForContact(aContact) {
+    let entries = [];
+    aContact.getBuddies().forEach(function (aBuddy) {
+      aBuddy.getAccountBuddies().forEach(function (aAccountBuddy) {
+        let file = getLogFolderForAccount(aAccountBuddy.account);
+        file.append(aAccountBuddy.normalizedName);
+        if (file.exists())
+          entries.push(file.directoryEntries);
+      });
+    });
+    return new LogEnumerator(entries);
+  },
+  getLogsForBuddy: function logger_getLogsForBuddy(aBuddy) {
+    let entries = [];
+    aBuddy.getAccountBuddies().forEach(function (aAccountBuddy) {
+      let file = getLogFolderForAccount(aAccountBuddy.account);
+      file.append(aAccountBuddy.normalizedName);
+      if (file.exists())
+        entries.push(file.directoryEntries);
+    });
+    return new LogEnumerator(entries);
+  },
+  getLogsForAccountBuddy: function logger_getLogsForAccountBuddy(aAccountBuddy)
+    this._enumerateLogs(aAccountBuddy.account, aAccountBuddy.normalizedName),
+  getLogsForConversation: function logger_getLogsForConversation(aConversation) {
+    let name = aConversation.normalizedName;
+    if (aConversation.isChat &&
+        aConversation.account.protocol.id != "prpl-twitter")
+      name += ".chat";
+    return this._enumerateLogs(aConversation.account, name);
+  },
+  getSystemLogsForAccount: function logger_getSystemLogsForAccount(aAccount)
+    this._enumerateLogs(aAccount, ".system"),
+  getSimilarLogs: function(aLog)
+    new LogEnumerator([new LocalFile(aLog.path).parent.directoryEntries]),
+
+  observe: function logger_observe(aSubject, aTopic, aData) {
+    switch (aTopic) {
+    case "profile-after-change":
+      Services.obs.addObserver(this, "final-ui-startup", false);
+      break;
+    case "final-ui-startup":
+      Services.obs.removeObserver(this, "final-ui-startup");
+      ["new-text", "conversation-closed", "conversation-left-chat",
+       "account-connected", "account-disconnected",
+       "account-buddy-status-changed"].forEach(function(aEvent) {
+        Services.obs.addObserver(this, aEvent, false);
+      }, this);
+      break;
+    case "new-text":
+      if (!aSubject.noLog) {
+        let log = getLogForConversation(aSubject.conversation);
+        log.logMessage(aSubject);
+      }
+      break;
+    case "conversation-closed":
+    case "conversation-left-chat":
+      closeLogForConversation(aSubject);
+      break;
+    case "account-connected":
+      getLogForAccount(aSubject, true).logEvent("+++ " + aSubject.name +
+                                                " signed on");
+      break;
+    case "account-disconnected":
+      getLogForAccount(aSubject).logEvent("+++ " + aSubject.name +
+                                          " signed off");
+      closeLogForAccount(aSubject);
+      break;
+    case "account-buddy-status-changed":
+      let status;
+      if (!aSubject.online)
+        status = "Offline";
+      else if (aSubject.mobile)
+        status = "Mobile";
+      else if (aSubject.idle)
+        status = "Idle";
+      else if (aSubject.available)
+        status = "Available";
+      else
+        status = "Unavailable";
+
+      let statusText = aSubject.statusText;
+      if (statusText)
+        status += " (\"" + statusText + "\")";
+
+      let nameText = aSubject.displayName + " (" + aSubject.userName + ")";
+      getLogForAccount(aSubject.account).logEvent(nameText + " is now " + status);
+      break;
+    default:
+      throw "Unexpected notification " + aTopic;
+    }
+  },
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.imILogger]),
+  classDescription: "Logger",
+  classID: Components.ID("{fb0dc220-2c7a-4216-9f19-6b8f3480eae9}"),
+  contractID: "@mozilla.org/chat/logger;1"
+};
+
+const NSGetFactory = XPCOMUtils.generateNSGetFactory([Logger]);
new file mode 100644
--- /dev/null
+++ b/chat/components/src/logger.manifest
@@ -0,0 +1,3 @@
+component {fb0dc220-2c7a-4216-9f19-6b8f3480eae9} logger.js
+contract @mozilla.org/chat/logger;1 {fb0dc220-2c7a-4216-9f19-6b8f3480eae9}
+category profile-after-change Logger @mozilla.org/chat/logger;1
new file mode 100644
--- /dev/null
+++ b/chat/components/src/smileProtocolHandler.js
@@ -0,0 +1,76 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2009.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource:///modules/imSmileys.jsm");
+
+const kSmileRegexp = /^smile:\/\//;
+
+function smileProtocolHandler() { }
+
+smileProtocolHandler.prototype = {
+  scheme: "smile",
+  defaultPort: -1,
+  protocolFlags: Ci.nsIProtocolHandler.URI_NORELATIVE |
+                 Ci.nsIProtocolHandler.URI_NOAUTH |
+                 Ci.nsIProtocolHandler.URI_IS_UI_RESOURCE |
+                 Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE,
+  newURI: function SPH_newURI(aSpec, aOriginCharset, aBaseURI) {
+    let uri = Cc["@mozilla.org/network/simple-uri;1"].createInstance(Ci.nsIURI);
+    uri.spec = aSpec;
+    uri.QueryInterface(Ci.nsIMutable);
+    uri.mutable = false;
+    return uri;
+  },
+  newChannel: function SPH_newChannel(aURI) {
+    let smile = aURI.spec.replace(kSmileRegexp, "");
+    let ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+    let channel = ios.newChannel(getSmileRealURI(smile), null, null);
+    channel.originalURI = aURI;
+    return channel;
+  },
+  allowPort: function  SPH_allowPort(aPort, aScheme) false,
+
+  classDescription: "Smile Protocol Handler",
+  classID: Components.ID("{04e58eae-dfbc-4c9e-8130-6d9ef19cbff4}"),
+  contractID: "@mozilla.org/network/protocol;1?name=smile",
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler])
+};
+
+const NSGetFactory = XPCOMUtils.generateNSGetFactory([smileProtocolHandler]);
new file mode 100644
--- /dev/null
+++ b/chat/components/src/smileProtocolHandler.manifest
@@ -0,0 +1,2 @@
+component {04e58eae-dfbc-4c9e-8130-6d9ef19cbff4} smileProtocolHandler.js
+contract @mozilla.org/network/protocol;1?name=smile {04e58eae-dfbc-4c9e-8130-6d9ef19cbff4}
new file mode 100644
--- /dev/null
+++ b/chat/content/Makefile.in
@@ -0,0 +1,44 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is the Mozilla Browser code.
+#
+# The Initial Developer of the Original Code is
+# Netscape Communications Corporation.
+# Portions created by the Initial Developer are Copyright (C) 2002
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH		= ../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/chat/content/browserRequest.js
@@ -0,0 +1,175 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is oauthorizer.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Shane Caraveo <shanec@mozillamessaging.com>
+ *   Florian Quèze <florian@instantbird.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const wpl = Components.interfaces.nsIWebProgressListener;
+
+var reporterListener = {
+  _isBusy: false,
+  get statusMeter() {
+    delete this.statusMeter;
+    return this.statusMeter = document.getElementById("statusbar-icon");
+  },
+  get securityButton() {
+    delete this.securityButton;
+    return this.securityButton = document.getElementById("security-button");
+  },
+  get securityLabel() {
+    delete this.securityLabel;
+    return this.securityLabel = document.getElementById("security-status");
+  },
+  get securityDisplay() {
+    delete this.securityDisplay;
+    return this.securityDisplay = document.getElementById("security-display");
+  },
+
+  QueryInterface: function(aIID) {
+    if (aIID.equals(Components.interfaces.nsIWebProgressListener)   ||
+        aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
+        aIID.equals(Components.interfaces.nsISupports))
+      return this;
+    throw Components.results.NS_NOINTERFACE;
+  },
+  onStateChange: function(/*in nsIWebProgress*/ aWebProgress,
+                     /*in nsIRequest*/ aRequest,
+                     /*in unsigned long*/ aStateFlags,
+                     /*in nsresult*/ aStatus) {
+    if (aStateFlags & wpl.STATE_START &&
+        aStateFlags & wpl.STATE_IS_NETWORK) {
+      this.statusMeter.value = 0;
+      this.statusMeter.parentNode.collapsed = false;
+      this.securityLabel.collapsed = true;
+    }
+    else if (aStateFlags & wpl.STATE_STOP &&
+             aStateFlags & wpl.STATE_IS_NETWORK) {
+      this.statusMeter.parentNode.collapsed = true;
+      this.securityLabel.collapsed = false;
+    }
+  },
+
+  onProgressChange: function(/*in nsIWebProgress*/ aWebProgress,
+                        /*in nsIRequest*/ aRequest,
+                        /*in long*/ aCurSelfProgress,
+                        /*in long */aMaxSelfProgress,
+                        /*in long */aCurTotalProgress,
+                        /*in long */aMaxTotalProgress) {
+    if (aMaxTotalProgress > 0) {
+      let percentage = (aCurTotalProgress * 100) / aMaxTotalProgress;
+      this.statusMeter.value = percentage;
+    }
+  },
+
+  onLocationChange: function(/*in nsIWebProgress*/ aWebProgress,
+                        /*in nsIRequest*/ aRequest,
+                        /*in nsIURI*/ aLocation) {
+    this.securityDisplay.setAttribute('label', aLocation.host);
+  },
+
+  onStatusChange: function(/*in nsIWebProgress*/ aWebProgress,
+                      /*in nsIRequest*/ aRequest,
+                      /*in nsresult*/ aStatus,
+                      /*in wstring*/ aMessage) {
+  },
+
+  onSecurityChange: function(/*in nsIWebProgress*/ aWebProgress,
+                        /*in nsIRequest*/ aRequest,
+                        /*in unsigned long*/ aState) {
+    const wpl_security_bits = wpl.STATE_IS_SECURE |
+                              wpl.STATE_IS_BROKEN |
+                              wpl.STATE_IS_INSECURE |
+                              wpl.STATE_SECURE_HIGH |
+                              wpl.STATE_SECURE_MED |
+                              wpl.STATE_SECURE_LOW;
+    let browser = document.getElementById("requestFrame");
+    let level;
+
+    switch (aState & wpl_security_bits) {
+      case wpl.STATE_IS_SECURE | wpl.STATE_SECURE_HIGH:
+        level = "high";
+        break;
+      case wpl.STATE_IS_SECURE | wpl.STATE_SECURE_MED:
+      case wpl.STATE_IS_SECURE | wpl.STATE_SECURE_LOW:
+        level = "low";
+        break;
+      case wpl.STATE_IS_BROKEN:
+        level = "broken";
+        break;
+    }
+    if (level) {
+      this.securityButton.setAttribute("level", level);
+      this.securityButton.hidden = false;
+      this.securityLabel.setAttribute("label", browser.securityUI.tooltipText);
+    } else {
+      this.securityButton.hidden = true;
+      this.securityButton.removeAttribute("level");
+    }
+    this.securityButton.setAttribute("tooltiptext",
+                                     browser.securityUI.tooltipText);
+  }
+}
+
+function cancelRequest()
+{
+  reportUserClosed();
+  window.close();
+}
+
+function reportUserClosed()
+{
+  let request = window.arguments[0];
+  request.QueryInterface(Components.interfaces.prplIRequestBrowser);
+  request.cancelled();
+}
+
+function loadRequestedUrl()
+{
+  let request = window.arguments[0];
+  request.QueryInterface(Components.interfaces.prplIRequestBrowser);
+  document.getElementById("headerMessage").textContent = request.promptText;
+  let account = request.account;
+  document.getElementById("headerLabel").value =
+    account.protocol.name + " - " + account.name;
+  document.getElementById("headerImage").src =
+    account.protocol.iconBaseURI + "icon48.png";
+
+  let browser = document.getElementById("requestFrame");
+  browser.addProgressListener(reporterListener,
+                              Components.interfaces.nsIWebProgress.NOTIFY_ALL);
+  let url = request.url;
+  if (url != "")
+    browser.setAttribute("src", url);
+  request.loaded(window, browser.webProgress);
+}
new file mode 100644
--- /dev/null
+++ b/chat/content/browserRequest.xul
@@ -0,0 +1,75 @@
+<?xml version="1.0"?>
+<!-- ***** BEGIN LICENSE BLOCK *****
+   - Version: MPL 1.1/GPL 2.0/LGPL 2.1
+   -
+   - The contents of this file are subject to the Mozilla Public License Version
+   - 1.1 (the "License"); you may not use this file except in compliance with
+   - the License. You may obtain a copy of the License at
+   - http://www.mozilla.org/MPL/
+   -
+   - Software distributed under the License is distributed on an "AS IS" basis,
+   - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+   - for the specific language governing rights and limitations under the
+   - License.
+   -
+   - The Original Code is oauthorizer.
+   -
+   - The Initial Developer of the Original Code is Mozilla.
+   - Portions created by the Initial Developer are Copyright (C) 2010
+   - the Initial Developer. All Rights Reserved.
+   -
+   - Contributor(s):
+   -   Shane Caraveo <shanec@mozillamessaging.com>
+   -   Florian Quèze <florian@instantbird.org>
+   -
+   - Alternatively, the contents of this file may be used under the terms of
+   - either the GNU General Public License Version 2 or later (the "GPL"), or
+   - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+   - in which case the provisions of the GPL or the LGPL are applicable instead
+   - of those above. If you wish to allow use of your version of this file only
+   - under the terms of either the GPL or the LGPL, and not to allow others to
+   - use your version of this file under the terms of the MPL, indicate your
+   - decision by deleting the provisions above and replace them with the notice
+   - and other provisions required by the LGPL or the GPL. If you do not delete
+   - the provisions above, a recipient may use your version of this file under
+   - the terms of any one of the MPL, the GPL or the LGPL.
+   -
+   - ***** END LICENSE BLOCK ***** -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://chat/skin/browserRequest.css" type="text/css"?>
+
+<!DOCTYPE window>
+<window id="browserRequest"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        buttons=","
+        onload="loadRequestedUrl()"
+        onclose="reportUserClosed()"
+        title=""
+        width="800"
+        height="500"
+        orient="vertical">
+
+  <script type="application/javascript" src="chrome://chat/content/browserRequest.js"/>
+
+  <keyset id="mainKeyset">
+    <key id="key_close"   key="w" modifiers="accel" oncommand="cancelRequest()"/>
+    <key id="key_close2"  keycode="VK_ESCAPE" oncommand="cancelRequest()"/>
+  </keyset>
+  <hbox id="header">
+    <image id="headerImage" src="chrome://chat/skin/prpl-generic/icon32.png"/>
+    <vbox>
+    <label id="headerLabel"/>
+    <description id="headerMessage"/>
+    </vbox>
+  </hbox>
+  <browser type="content" disablehistory="true" src="about:blank" id="requestFrame" flex="1"/>
+  <statusbar>
+    <statusbarpanel id="security-display" crop="end" flex="1"/>
+    <statusbarpanel id="security-status" crop="end" collapsed="true"/>
+    <statusbarpanel class="statusbarpanel-progress" collapsed="true" id="statusbar-status">
+      <progressmeter class="progressmeter-statusbar" id="statusbar-icon" mode="normal" value="0"/>
+    </statusbarpanel>
+    <statusbarpanel id="security-button" class="statusbarpanel-iconic"/>
+  </statusbar>
+</window>
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/chat/content/convbrowser.xml
@@ -0,0 +1,1036 @@
+<?xml version="1.0"?>
+
+<!-- ***** BEGIN LICENSE BLOCK *****
+   - Version: MPL 1.1/GPL 2.0/LGPL 2.1
+   -
+   - The contents of this file are subject to the Mozilla Public License Version
+   - 1.1 (the "License"); you may not use this file except in compliance with
+   - the License. You may obtain a copy of the License at
+   - http://www.mozilla.org/MPL/
+   -
+   - Software distributed under the License is distributed on an "AS IS" basis,
+   - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+   - for the specific language governing rights and limitations under the
+   - License.
+   -
+   - The Original Code is this file as it was released on March 28, 2001.
+   -
+   - The Initial Developer of the Original Code is
+   - Peter Annema.
+   - Portions created by the Initial Developer are Copyright (C) 2001
+   - the Initial Developer. All Rights Reserved.
+   -
+   - Contributor(s):
+   -   Peter Annema <disttsc@bart.nl> (Original Author of <browser>)
+   -   Peter Parente <parente@cs.unc.edu>
+   -   Christopher Thomas <cst@yecc.com>
+   -   Michael Ventnor <m.ventnor@gmail.com>
+   -   Florian Queze <florian@instantbird.org>
+   -
+   - Alternatively, the contents of this file may be used under the terms of
+   - either the GNU General Public License Version 2 or later (the "GPL"), or
+   - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+   - in which case the provisions of the GPL or the LGPL are applicable instead
+   - of those above. If you wish to allow use of your version of this file only
+   - under the terms of either the GPL or the LGPL, and not to allow others to
+   - use your version of this file under the terms of the MPL, indicate your
+   - decision by deleting the provisions above and replace them with the notice
+   - and other provisions required by the GPL or the LGPL. If you do not delete
+   - the provisions above, a recipient may use your version of this file under
+   - the terms of any one of the MPL, the GPL or the LGPL.
+   -
+   - ***** END LICENSE BLOCK ***** -->
+
+<bindings id="browserBindings"
+          xmlns="http://www.mozilla.org/xbl"
+          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+  <binding id="browser">
+    <implementation type="application/javascript"
+                    implements="nsIAccessibleProvider, nsIDOMEventListener,
+                                nsIWebProgressListener, nsIController,
+                                nsISelectionListener, nsIObserver">
+      <property name="accessibleType" readonly="true">
+        <getter>
+          <![CDATA[
+            return Components.interfaces.nsIAccessibleProvider.OuterDoc;
+          ]]>
+        </getter>
+      </property>
+
+      <property name="autoscrollEnabled">
+        <getter>
+          <![CDATA[
+            if (this.getAttribute("autoscroll") == "false")
+              return false;
+
+            var enabled = true;
+            try {
+              enabled = Services.prefs.getBoolPref("general.autoScroll");
+            }
+            catch(ex) {
+            }
+
+            return enabled;
+          ]]>
+        </getter>
+      </property>
+
+      <field name="_theme">null</field>
+
+      <property name="theme"
+                readonly="true"
+                onget="return this._theme || (this._theme = getCurrentTheme());;"/>
+
+      <field name="_conv">null</field>
+
+      <field name="_loadState">0</field>
+
+      <method name="init">
+        <parameter name="aConversation"/>
+        <body>
+          <![CDATA[
+            this._conv = aConversation;
+
+            // init is called when the message style preview is
+            // reloaded so we need to reset _theme.
+            this._theme = null;
+
+            // Prevent ongoing asynchronous message display from continuing.
+            this._messageDisplayPending = false;
+
+            // _loadState is 0 while loading conv.html and 1 while
+            // loading the real conversation HTML.
+            this._loadState = 0;
+
+            const URI = "chrome://chat/content/conv.html";
+            const flag = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
+            this.webNavigation.loadURI(URI, flag, null, null, null);
+            this.addProgressListener(this);
+
+            if (this._scrollingView)
+              this._autoScrollPopup.hidePopup();
+          ]]>
+        </body>
+      </method>
+
+      <property name="currentURI"
+                onget="return this.webNavigation.currentURI;"
+                readonly="true"/>
+
+      <field name="_docShell">null</field>
+
+      <property name="docShell"
+                onget="return this._docShell || (this._docShell = this.boxObject.QueryInterface(Components.interfaces.nsIContainerBoxObject).docShell);"
+                readonly="true"/>
+
+      <field name="_webNavigation">null</field>
+
+      <property name="webNavigation"
+                onget="return this._webNavigation || (this._webNavigation = this.docShell.QueryInterface(Components.interfaces.nsIWebNavigation));"
+                readonly="true"/>
+
+
+      <field name="_fastFind">null</field>
+      <property name="fastFind"
+                readonly="true">
+        <getter>
+        <![CDATA[
+          if (!this._fastFind) {
+            if (!("@mozilla.org/typeaheadfind;1" in Components.classes))
+              return null;
+
+            if (!this.docShell)
+              return null;
+
+            this._fastFind = Components.classes["@mozilla.org/typeaheadfind;1"]
+                                       .createInstance(Components.interfaces.nsITypeAheadFind);
+            this._fastFind.init(this.docShell);
+          }
+          return this._fastFind;
+        ]]>
+        </getter>
+      </property>
+
+      <property name="webProgress"
+                readonly="true"
+                onget="return this.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIWebProgress);"/>
+
+      <field name="_contentWindow">null</field>
+
+      <property name="contentWindow"
+                readonly="true"
+                onget="return this._contentWindow || (this._contentWindow = this.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindow));"/>
+
+      <property name="contentDocument"
+                onget="return this.webNavigation.document;"
+                readonly="true"/>
+
+      <property name="markupDocumentViewer"
+                onget="return this.docShell.contentViewer.QueryInterface(Components.interfaces.nsIMarkupDocumentViewer);"
+                readonly="true"/>
+
+      <field name="magicCopyPref" readonly="true">"messenger.conversations.selections.magicCopyEnabled"</field>
+
+      <property name="magicCopyEnabled"
+                onget="return Services.prefs.getBoolPref(this.magicCopyPref);"
+                readonly="true"/>
+
+      <method name="addProgressListener">
+        <parameter name="aListener"/>
+        <body>
+          <![CDATA[
+            this.webProgress.addProgressListener(aListener, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
+          ]]>
+        </body>
+      </method>
+
+      <method name="removeProgressListener">
+        <parameter name="aListener"/>
+        <body>
+          <![CDATA[
+            this.webProgress.removeProgressListener(aListener);
+         ]]>
+        </body>
+      </method>
+
+      <field name="mDestroyed">false</field>
+
+      <constructor>
+        <![CDATA[
+          this.addEventListener("scroll", this.browserScroll);
+          this.addEventListener("resize", this.browserResize);
+          Services.prefs.addObserver(this.magicCopyPref, this, false);
+
+          if (!("cleanupImMarkup" in window))
+            Components.utils.import("resource:///modules/imContentSink.jsm");
+          if (!("smileImMarkup" in window))
+            Components.utils.import("resource:///modules/imSmileys.jsm");
+          if (!("getCurrentTheme" in window))
+            Components.utils.import("resource:///modules/imThemes.jsm");
+        ]]>
+      </constructor>
+
+      <destructor>
+        <![CDATA[
+          this.destroy();
+        ]]>
+      </destructor>
+
+      <!-- This is necessary because the destructor doesn't always get called when
+           we are removed from a tabbrowser. This will be explicitly called by tabbrowser -->
+      <method name="destroy">
+        <body>
+          <![CDATA[
+            if (this.mDestroyed)
+              return;
+            this.mDestroyed = true;
+            this._messageDisplayPending = false;
+
+            if (this.mDragDropHandler)
+              this.mDragDropHandler.detach();
+            this.mDragDropHandler = null;
+
+            this._fastFind = null;
+
+            if (this._autoScrollNeedsCleanup) {
+              // we polluted the global scope, so clean it up
+              this._autoScrollPopup.parentNode.removeChild(this._autoScrollPopup);
+            }
+
+            Services.prefs.removeObserver(this.magicCopyPref, this);
+            if (this.magicCopyEnabled)
+              this.contentWindow.controllers.removeController(this);
+          ]]>
+        </body>
+      </method>
+
+      <field name="_autoScrollEnabled">true</field>
+
+      <method name="_updateAutoScrollEnabled">
+        <body>
+          <![CDATA[
+            // Enable auto-scroll if the scrollbar is at the bottom.
+            let body = this.contentDocument.getElementsByTagName("body")[0];
+            this._autoScrollEnabled =
+              body.scrollHeight <= body.scrollTop + body.clientHeight + 10;
+            return this._autoScrollEnabled;
+          ]]>
+         </body>
+      </method>
+
+      <method name="_scrollToElement">
+        <parameter name="aElt"/>
+        <body>
+          <![CDATA[
+            aElt.scrollIntoView(true);
+            this._scrollingIntoView = true;
+          ]]>
+         </body>
+      </method>
+
+      <field name="_textModifiers">[smileTextNode]</field>
+
+      <method name="addTextModifier">
+        <parameter name="aModifier"/>
+        <body>
+          <![CDATA[
+            if (this._textModifiers.indexOf(aModifier) == -1)
+              this._textModifiers.push(aModifier);
+          ]]>
+        </body>
+      </method>
+
+      <!-- These variables are reset in onStateChange. -->
+      <field name="_lastMessage">null</field>
+      <field name="_lastMessageIsContext">true</field>
+      <field name="_firstNonContextElt">null</field>
+
+      <field name="_pendingMessages">[]</field>
+      <field name="_messageDisplayPending">false</field>
+      <method name="appendMessage">
+        <parameter name="aMsg"/>
+        <parameter name="aContext"/>
+        <body>
+          <![CDATA[
+            this._pendingMessages.push({msg: aMsg, context: aContext});
+            if (this._messageDisplayPending)
+              return;
+            this._messageDisplayPending = true;
+            Services.tm.mainThread.dispatch(this.displayPendingMessages.bind(this),
+                                            Ci.nsIEventTarget.DISPATCH_NORMAL);
+          ]]>
+        </body>
+      </method>
+
+      <field name="progressBar">null</field>
+      <!-- These variables are reset in onStateChange. -->
+      <field name="_nextPendingMessageIndex">0</field>
+      <field name="_displayPendingMessagesCalls">0</field>
+      <method name="displayPendingMessages">
+        <body>
+          <![CDATA[
+            if (!this._messageDisplayPending)
+              return;
+
+            let max = this._pendingMessages.length;
+            let begin = Date.now();
+            let i;
+            for (i = this._nextPendingMessageIndex; i < max; ++i) {
+              let msg = this._pendingMessages[i];
+              this.displayMessage(msg.msg, msg.context, i + 1 < max);
+              if (Date.now() - begin > 40)
+                break;
+            }
+            if (i < max - 1) {
+              this._nextPendingMessageIndex = i + 1;
+              if (this.progressBar) {
+                // Show progress bar if after the third call (ca. 120ms)
+                // less than half the messages have been displayed.
+                if (++this._displayPendingMessagesCalls > 2 &&
+                    max > 2 * this._nextPendingMessageIndex)
+                  this.progressBar.hidden = false;
+                this.progressBar.max = max;
+                this.progressBar.value = this._nextPendingMessageIndex;
+              }
+              Services.tm.mainThread.dispatch(this.displayPendingMessages.bind(this),
+                                              Ci.nsIEventTarget.DISPATCH_NORMAL);
+              return;
+            }
+            this._messageDisplayPending = false;
+            this._pendingMessages = [];
+            this._nextPendingMessageIndex = 0;
+            this._displayPendingMessagesCalls = 0;
+            if (this.progressBar)
+              this.progressBar.hidden = true;
+          ]]>
+        </body>
+      </method>
+
+      <method name="displayMessage">
+        <parameter name="aMsg"/>
+        <parameter name="aContext"/>
+        <parameter name="aNoAutoScroll"/>
+        <body>
+          <![CDATA[
+            let doc = this.contentDocument;
+            let cs = Components.classes["@mozilla.org/txttohtmlconv;1"]
+                               .getService(Ci.mozITXTToHTMLConv);
+            /*
+             * kStructPhrase creates tags for plaintext-markup like *bold*,
+             * /italics/, etc. We always use this; the content filter will
+             * filter it out if the user does not want styling.
+             */
+            let csFlags = cs.kStructPhrase;
+            // Automatically find and link freetext URLs
+            if (!aMsg.noLinkification)
+              csFlags |= cs.kURLs;
+
+            let msg = aMsg.originalMessage;
+
+            // The slash of a leading '/me' should not be used to
+            // format as italic, so we remove the '/me' text before
+            // scanning the HTML, and we add it back later.
+            let meRegExp = /^((<[^>]+>)*)\/me /;
+            let me = false;
+            if (meRegExp.test(msg)) {
+              me = true;
+              msg = msg.replace(meRegExp, "$1");
+            }
+
+            msg = cs.scanHTML(msg.replace(/&/g, "&amp;"), csFlags)
+                    .replace(/&amp;/g, "&");
+
+            if (me)
+              msg = msg.replace(/^((<[^>]+>)*)/, "$1/me ");
+
+            aMsg.message = cleanupImMarkup(doc, msg.replace(/\r?\n/g, "<br/>"),
+                                           null, this._textModifiers);
+            let next = (aContext == this._lastMessageIsContext || aMsg.system) &&
+                       isNextMessage(this.theme, aMsg, this._lastMessage);
+            let html = getHTMLForMessage(aMsg, this.theme, next, aContext);
+            let body = doc.getElementsByTagName("body")[0];
+            let newElt = insertHTMLForMessage(aMsg, html, doc, next);
+            if (!aNoAutoScroll) {
+              newElt.getBoundingClientRect(); // avoid ireflow bugs
+              if (this._autoScrollEnabled || this._updateAutoScrollEnabled())
+                this._scrollToElement(newElt);
+            }
+            this._lastElement = newElt;
+            this._lastMessage = aMsg;
+            if (!aContext && !this._firstNonContextElt && !aMsg.system)
+              this._firstNonContextElt = newElt;
+            this._lastMessageIsContext = aContext;
+          ]]>
+        </body>
+      </method>
+
+      <method name="scrollToPreviousSection">
+        <body>
+        <![CDATA[
+          let nonContextY =
+            this._firstNonContextElt ? this._firstNonContextElt.offsetTop : 0;
+          let sectionY =
+            Math.max(nonContextY - Math.round(this.clientHeight / 2), 0);
+          if (this.contentWindow.scrollY < nonContextY)
+            this.contentWindow.scrollTo(0, 0);
+          else
+            this.contentWindow.scrollTo(0, sectionY);
+          this._updateAutoScrollEnabled();
+        ]]>
+        </body>
+      </method>
+
+      <method name="scrollToNextSection">
+        <body>
+        <![CDATA[
+          let maxY = this.contentWindow.scrollMaxY;
+          let nonContextY =
+            this._firstNonContextElt ? this._firstNonContextElt.offsetTop : maxY;
+          let sectionY =
+            (nonContextY < maxY) ? nonContextY - Math.round(this.clientHeight / 2) : maxY;
+          if (this.contentWindow.scrollY < sectionY)
+            this.contentWindow.scrollTo(0, sectionY);
+          else
+            this.contentWindow.scrollTo(0, maxY);
+          this._updateAutoScrollEnabled();
+        ]]>
+        </body>
+      </method>
+
+      <method name="browserScroll">
+        <parameter name="event"/>
+        <body>
+          <![CDATA[
+            if (this._scrollingIntoView) {
+              // We have explicitely requested a scrollIntoView, ignore the event
+              this._scrollingIntoView = false;
+              this._lastScrollHeight = this.scrollHeight;
+              this._lastScrollWidth = this.scrollWidth;
+              return;
+            }
+
+            if (this._lastScrollHeight != this.scrollHeight ||
+                this._lastScrollWidth != this.scrollWidth) {
+              // if the scrollheight changed, we are resizing the content area,
+              // don't stop the auto scroll.
+              this._lastScrollHeight = this.scrollHeight;
+              this._lastScrollWidth = this.scrollWidth;
+              return;
+            }
+
+            // Enable or disable auto-scroll based on the scrollbar position
+            this._updateAutoScrollEnabled();
+          ]]>
+         </body>
+      </method>
+
+      <method name="browserResize">
+        <parameter name="event"/>
+        <body>
+          <![CDATA[
+            if (this._autoScrollEnabled && this._lastElement) {
+              // The content area was resized and auto-scroll is enabled,
+              // make sure the last inserted element is still visible
+              this._scrollToElement(this._lastElement);
+            }
+          ]]>
+        </body>
+      </method>
+
+      <!-- nsIObserver implementation -->
+      <method name="observe">
+        <parameter name="aSubject"/>
+        <parameter name="aTopic"/>
+        <parameter name="aData"/>
+        <body>
+        <![CDATA[
+          if (aTopic != "nsPref:changed")
+            throw "Bad notification";
+
+          var clipboard =
+            Components.classes["@mozilla.org/widget/clipboard;1"]
+                      .getService(Components.interfaces.nsIClipboard);
+
+          if (this.magicCopyEnabled) {
+            this.contentWindow.controllers.insertControllerAt(0, this);
+            if (clipboard.supportsSelectionClipboard()) {
+              this.contentWindow.getSelection()
+                  .QueryInterface(Components.interfaces.nsISelectionPrivate)
+                  .addSelectionListener(this);
+            }
+          }
+          else {
+            this.contentWindow.controllers.removeController(this);
+            if (clipboard.supportsSelectionClipboard()) {
+               this.contentWindow.getSelection()
+                   .QueryInterface(Components.interfaces.nsISelectionPrivate)
+                   .removeSelectionListener(this);
+            }
+          }
+        ]]>
+        </body>
+      </method>
+
+      <!-- nsIController implementation -->
+      <method name="supportsCommand">
+        <parameter name="aCommand"/>
+        <body>
+          <![CDATA[
+            return aCommand == "cmd_copy" || aCommand == "cmd_cut";
+          ]]>
+        </body>
+      </method>
+
+      <method name="isCommandEnabled">
+        <parameter name="aCommand"/>
+        <body>
+          <![CDATA[
+            return aCommand == "cmd_copy" &&
+                   !this.contentWindow.getSelection().isCollapsed;
+          ]]>
+        </body>
+      </method>
+
+      <method name="doCommand">
+        <parameter name="aCommand"/>
+        <body>
+          <![CDATA[
+            let selection = this.contentWindow.getSelection();
+            if (selection.isCollapsed)
+              return;
+
+            Components.classes["@mozilla.org/widget/clipboardhelper;1"]
+                      .getService(Ci.nsIClipboardHelper)
+                      .copyString(serializeSelection(selection));
+          ]]>
+        </body>
+      </method>
+
+      <method name="onEvent">
+        <parameter name="aCommand"/>
+      </method>
+
+      <!-- nsISelectionListener implementation -->
+      <method name="notifySelectionChanged">
+        <parameter name="aDocument"/>
+        <parameter name="aSelection"/>
+        <parameter name="aReason"/>
+        <body>
+          <![CDATA[
+            if (!(aReason & Ci.nsISelectionListener.MOUSEUP_REASON   ||
+                  aReason & Ci.nsISelectionListener.SELECTALL_REASON ||
+                  aReason & Ci.nsISelectionListener.KEYPRESS_REASON))
+              return; // we are still dragging, don't bother with the selection
+
+            Components.classes["@mozilla.org/widget/clipboardhelper;1"]
+                      .getService(Ci.nsIClipboardHelper)
+                      .copyStringToClipboard(serializeSelection(aSelection),
+                                             Ci.nsIClipboard.kSelectionClipboard);
+          ]]>
+        </body>
+      </method>
+
+      <!-- nsIWebProgressListener implementation -->
+      <method name="onStateChange">
+        <parameter name="aProgress"/>
+        <parameter name="aRequest"/>
+        <parameter name="aStateFlags"/>
+        <parameter name="aStatus"/>
+        <body>
+          <![CDATA[
+            const WPL = Components.interfaces.nsIWebProgressListener;
+            if ((aStateFlags & WPL.STATE_IS_DOCUMENT) &&
+                (aStateFlags & WPL.STATE_STOP)) {
+              if (!this._loadState) {
+                try {
+                  initHTMLDocument(this._conv, this.theme, this.contentDocument);
+                } catch(e) {
+                  Components.utils.reportError(e);
+                }
+                this._loadState = 1;
+                return;
+              }
+              this.removeProgressListener(this);
+
+              if (this.magicCopyEnabled) {
+                this.contentWindow.controllers.insertControllerAt(0, this);
+
+                var clipboard =
+                  Components.classes["@mozilla.org/widget/clipboard;1"]
+                            .getService(Components.interfaces.nsIClipboard);
+                if (clipboard.supportsSelectionClipboard()) {
+                  this.contentWindow.getSelection()
+                      .QueryInterface(Components.interfaces.nsISelectionPrivate)
+                      .addSelectionListener(this);
+                }
+              }
+
+              // We need to reset these variables here to avoid a race
+              // condition if we are starting to display a new conversation
+              // but the display of the previous conversation wasn't finished.
+              // This can happen if the user quickly changes the selected
+              // conversation in the log viewer.
+              this._lastMessage = null;
+              this._lastMessageIsContext = true;
+              this._firstNonContextElt = null;
+              this._messageDisplayPending = false;
+              this._pendingMessages = [];
+              this._nextPendingMessageIndex = 0;
+              this._displayPendingMessagesCalls = 0;
+
+              Services.obs.notifyObservers(this, "conversation-loaded", null);
+            }
+          ]]>
+        </body>
+      </method>
+
+      <method name="onProgressChange">
+        <parameter name="aProgress"/>
+        <parameter name="aRequest"/>
+        <parameter name="aCurSelf"/>
+        <parameter name="aMaxSelf"/>
+        <parameter name="aCurTotal"/>
+        <parameter name="aMaxTotal"/>
+      </method>
+
+      <method name="onLocationChange">
+        <parameter name="aProgress"/>
+        <parameter name="aRequest"/>
+        <parameter name="aLocation"/>
+      </method>
+
+      <method name="onStatusChange">
+        <parameter name="aProgress"/>
+        <parameter name="aRequest"/>
+        <parameter name="aStatus"/>
+        <parameter name="aMessage"/>
+      </method>
+
+      <method name="onSecurityChange">
+        <parameter name="aProgress"/>
+        <parameter name="aRequest"/>
+        <parameter name="aState"/>
+      </method>
+
+
+      <!-- autoscroll stuff -->
+      <field name="_AUTOSCROLL_SPEED">3</field>
+      <field name="_AUTOSCROLL_SNAP">10</field>
+      <field name="_scrollingView">null</field>
+      <field name="_autoScrollTimer">null</field>
+      <field name="_startX">null</field>
+      <field name="_startY">null</field>
+      <field name="_screenX">null</field>
+      <field name="_screenY">null</field>
+      <field name="_autoScrollPopup">null</field>
+      <field name="_autoScrollNeedsCleanup">false</field>
+
+      <method name="stopScroll">
+        <body>
+          <![CDATA[
+            if (this._scrollingView) {
+              this._scrollingView = null;
+              window.removeEventListener("mousemove", this, true);
+              window.removeEventListener("mousedown", this, true);
+              window.removeEventListener("mouseup", this, true);
+              window.removeEventListener("contextmenu", this, true);
+              clearInterval(this._autoScrollTimer);
+            }
+         ]]>
+       </body>
+      </method>
+
+      <method name="_createAutoScrollPopup">
+        <body>
+          <![CDATA[
+            const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+            var popup = document.createElementNS(XUL_NS, "popup");
+            popup.className = "autoscroller";
+            return popup;
+          ]]>
+        </body>
+      </method>
+
+      <method name="startScroll">
+        <parameter name="event"/>
+        <body>
+          <![CDATA[
+            if (!this._autoScrollPopup) {
+              if (this.hasAttribute("autoscrollpopup")) {
+                // our creator provided a popup to share
+                this._autoScrollPopup = document.getElementById(this.getAttribute("autoscrollpopup"));
+              }
+              else {
+                // we weren't provided a popup; we have to use the global scope
+                this._autoScrollPopup = this._createAutoScrollPopup();
+                document.documentElement.appendChild(this._autoScrollPopup);
+                this._autoScrollNeedsCleanup = true;
+              }
+            }
+
+            this._autoScrollPopup.addEventListener("popuphidden", this, true);
+
+            // we need these attributes so themers don't need to create per-platform packages
+            if (screen.colorDepth > 8) { // need high color for transparency
+              // Exclude second-rate platforms
+              this._autoScrollPopup.setAttribute("transparent", !/BeOS|OS\/2|Photon/.test(navigator.appVersion));
+              // Enable translucency on Windows and Mac
+              this._autoScrollPopup.setAttribute("translucent", /Win|Mac/.test(navigator.platform));
+            }
+
+            this._scrollingView = event.originalTarget.ownerDocument.defaultView;
+            if (this._scrollingView.scrollMaxX > 0) {
+              this._autoScrollPopup.setAttribute("scrolldir", this._scrollingView.scrollMaxY > 0 ? "NSEW" : "EW");
+            }
+            else if (this._scrollingView.scrollMaxY > 0) {
+              this._autoScrollPopup.setAttribute("scrolldir", "NS");
+            }
+            else {
+              this._scrollingView = null; // abort scrolling
+              return;
+            }
+
+            document.popupNode = null;
+            this._autoScrollPopup.showPopup(document.documentElement,
+                                            event.screenX,
+                                            event.screenY,
+                                            "popup", null, null);
+            this._ignoreMouseEvents = true;
+            this._startX = event.screenX;
+            this._startY = event.screenY;
+            this._screenX = event.screenX;
+            this._screenY = event.screenY;
+
+            window.addEventListener("mousemove", this, true);
+            window.addEventListener("mousedown", this, true);
+            window.addEventListener("mouseup", this, true);
+            window.addEventListener("contextmenu", this, true);
+
+            this._scrollErrorX = 0;
+            this._scrollErrorY = 0;
+
+            this._autoScrollTimer = setInterval(function(self) { self.autoScrollLoop(); },
+                                                20, this);
+         ]]>
+        </body>
+      </method>
+
+      <method name="_roundToZero">
+        <parameter name="num"/>
+        <body>
+          <![CDATA[
+            if (num > 0)
+              return Math.floor(num);
+            return Math.ceil(num);
+          ]]>
+        </body>
+      </method>
+
+      <method name="_accelerate">
+        <parameter name="curr"/>
+        <parameter name="start"/>
+        <body>
+          <![CDATA[
+            const kSpeed = 12;
+            var val = (curr - start) / kSpeed;
+
+            if (val > 1)
+              return val * Math.sqrt(val) - 1;
+            if (val < -1)
+              return val * Math.sqrt(-val) + 1;
+            return 0;
+          ]]>
+        </body>
+      </method>
+
+      <method name="autoScrollLoop">
+        <body>
+          <![CDATA[
+            var x = this._accelerate(this._screenX, this._startX);
+            var y = this._accelerate(this._screenY, this._startY);
+
+            var desiredScrollX = this._scrollErrorX + x;
+            var actualScrollX = this._roundToZero(desiredScrollX);
+            this._scrollErrorX = (desiredScrollX - actualScrollX);
+            var desiredScrollY = this._scrollErrorY + y;
+            var actualScrollY = this._roundToZero(desiredScrollY);
+            this._scrollErrorY = (desiredScrollY - actualScrollY);
+
+            this._scrollingView.scrollBy(actualScrollX, actualScrollY);
+          ]]>
+        </body>
+      </method>
+
+      <method name="isAutoscrollBlocker">
+        <parameter name="node"/>
+        <body>
+          <![CDATA[
+            var mmPaste = false;
+            var mmScrollbarPosition = false;
+
+            try {
+              mmPaste = Services.prefs.getBoolPref("middlemouse.paste");
+            }
+            catch (ex) {
+            }
+
+            try {
+              mmScrollbarPosition = Services.prefs.getBoolPref("middlemouse.scrollbarPosition");
+            }
+            catch (ex) {
+            }
+
+            while (node) {
+              if ((node instanceof HTMLAnchorElement || node instanceof HTMLAreaElement) && node.hasAttribute("href"))
+                return true;
+
+              if (mmPaste && (node instanceof HTMLInputElement || node instanceof HTMLTextAreaElement))
+                return true;
+
+              if (node instanceof XULElement && mmScrollbarPosition
+                  && (node.localName == "scrollbar" || node.localName == "scrollcorner"))
+                return true;
+
+              node = node.parentNode;
+            }
+            return false;
+          ]]>
+        </body>
+      </method>
+
+      <method name="handleEvent">
+        <parameter name="aEvent"/>
+        <body>
+          <![CDATA[
+            if (this._scrollingView) {
+              switch(aEvent.type) {
+                case "mousemove": {
+                  this._screenX = aEvent.screenX;
+                  this._screenY = aEvent.screenY;
+
+                  var x = this._screenX - this._startX;
+                  var y = this._screenY - this._startY;
+
+                  if ((x > this._AUTOSCROLL_SNAP || x < -this._AUTOSCROLL_SNAP) ||
+                      (y > this._AUTOSCROLL_SNAP || y < -this._AUTOSCROLL_SNAP))
+                    this._ignoreMouseEvents = false;
+                  break;
+                }
+                case "mouseup":
+                case "mousedown":
+                case "contextmenu": {
+                  if (!this._ignoreMouseEvents)
+                    this._autoScrollPopup.hidePopup();
+                  this._ignoreMouseEvents = false;
+                  break;
+                }
+                case "popuphidden": {
+                  this._autoScrollPopup.removeEventListener("popuphidden", this, true);
+                  this.stopScroll();
+                  break;
+                }
+              }
+            }
+          ]]>
+        </body>
+      </method>
+
+      <method name="swapDocShells">
+        <parameter name="aOtherBrowser"/>
+        <body>
+        <![CDATA[
+          aOtherBrowser.destroy();
+
+          var magicCopyEnabled = this.magicCopyEnabled;
+
+          if (magicCopyEnabled) {
+            // We need to remove the selection listener (unix only)
+            // before _contentWindow is changed.
+            var clipboard = Components.classes["@mozilla.org/widget/clipboard;1"]
+                                      .getService(Ci.nsIClipboard);
+            if (clipboard.supportsSelectionClipboard()) {
+              aOtherBrowser.contentWindow.getSelection()
+                           .QueryInterface(Components.interfaces.nsISelectionPrivate)
+                           .removeSelectionListener(aOtherBrowser);
+            }
+          }
+
+          // and the progress listener too!
+          this.removeProgressListener(this);
+
+          // We need to swap fields that are tied to our docshell or related to
+          // the loaded page
+          // Fields which are built as a result of notifactions (pageshow/hide,
+          // DOMLinkAdded/Removed, onStateChange) should not be swapped here,
+          // because these notifications are dispatched again once the docshells
+          // are swapped.
+          var fieldsToSwap = ["_docShell", "_fastFind", "_contentWindow", "_webNavigation",
+                              "_theme", "_textModifiers", "_autoScrollEnabled",
+                              "_lastElement", "_lastMessage", "_lastMessageIsContext",
+                              "_firstNonContextElt"];
+
+          var ourFieldValues = {};
+          var otherFieldValues = {};
+          for each (var field in fieldsToSwap) {
+            ourFieldValues[field] = this[field];
+            otherFieldValues[field] = aOtherBrowser[field];
+          }
+          this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner)
+              .swapFrameLoaders(aOtherBrowser);
+          for each (var field in fieldsToSwap) {
+            this[field] = otherFieldValues[field];
+            aOtherBrowser[field] = ourFieldValues[field];
+          }
+
+          if (!magicCopyEnabled)
+            return;
+
+          // Now that we have the new _contentWindow, we can add back our controller
+          this.contentWindow.controllers.insertControllerAt(0, this);
+
+          // and our selection listener!
+          if (clipboard.supportsSelectionClipboard()) {
+            this.contentWindow.getSelection()
+                .QueryInterface(Components.interfaces.nsISelectionPrivate)
+                .addSelectionListener(this);
+          }
+        ]]>
+        </body>
+      </method>
+    </implementation>
+
+    <handlers>
+      <handler event="click">
+        <![CDATA[
+          // Right click should open the context menu.
+          if (event.button == 2)
+            return;
+
+          // The 'click' event is fired even when the link is
+          // activated with the keyboard.
+
+          // The event target may be a descendant of the actual link.
+          let url;
+          for (let elem = event.target; elem; elem = elem.parentNode) {
+            if (elem instanceof HTMLAnchorElement) {
+              url = elem.href;
+              if (url)
+                break;
+            }
+          }
+          if (url) {
+            let uri = Services.io.newURI(url, null, null);
+
+            // http and https are the only schemes that are both
+            // allowed by our IM filters and exposed.
+            if (!uri.schemeIs("http") && !uri.schemeIs("https"))
+              return;
+
+            event.preventDefault();
+            event.stopPropagation();
+
+            // loadUrl can throw if the default browser is misconfigured.
+            Components.classes["@mozilla.org/uriloader/external-protocol-service;1"]
+                      .getService(Components.interfaces.nsIExternalProtocolService)
+                      .loadUrl(uri);
+          }
+        ]]>
+      </handler>
+
+      <handler event="keypress" modifiers="shift" keycode="VK_PAGE_UP"
+               action="this.docShell.QueryInterface(Components.interfaces.nsITextScroll).scrollByPages(-1);"/>
+
+      <handler event="keypress" modifiers="shift" keycode="VK_PAGE_DOWN"
+               action="this.docShell.QueryInterface(Components.interfaces.nsITextScroll).scrollByPages(1);"/>
+
+      <handler event="keypress" modifiers="alt" keycode="VK_PAGE_UP"
+               action="this.scrollToPreviousSection();"/>
+
+      <handler event="keypress" modifiers="alt" keycode="VK_PAGE_DOWN"
+               action="this.scrollToNextSection();"/>
+
+      <handler event="keypress" keycode="VK_HOME"
+               action="this.scrollToPreviousSection(); event.preventDefault();"/>
+
+      <handler event="keypress" keycode="VK_END"
+               action="this.scrollToNextSection(); event.preventDefault();"/>
+
+      <handler event="keypress" modifiers="control" keycode="VK_HOME"
+               action="this.scrollToPreviousSection(); event.preventDefault();"/>
+
+      <handler event="keypress" modifiers="control" keycode="VK_END"
+               action="this.scrollToNextSection(); event.preventDefault();"/>
+
+      <handler event="keypress" keycode="VK_F7" group="system">
+        <![CDATA[
+          if (event.getPreventDefault() || !event.isTrusted)
+            return;
+
+          var isEnabled = Services.prefs.getBoolPref("accessibility.browsewithcaret_shortcut.enabled");
+          if (!isEnabled)
+            return;
+
+          // Toggle browse with caret mode
+          try {
+            var browseWithCaretOn = Services.prefs.getBoolPref("accessibility.browsewithcaret");
+            Services.prefs.setBoolPref("accessibility.browsewithcaret", !browseWithCaretOn);
+          } catch (ex) {
+          }
+        ]]>
+      </handler>
+
+      <handler event="mousedown" phase="capturing">
+        <![CDATA[
+          if (!this._scrollingView && event.button == 1) {
+            if (!this.autoscrollEnabled  ||
+                this.isAutoscrollBlocker(event.originalTarget))
+              return;
+
+            this.startScroll(event);
+          }
+        ]]>
+      </handler>
+    </handlers>
+  </binding>
+</bindings>
new file mode 100644
--- /dev/null
+++ b/chat/content/jar.mn
@@ -0,0 +1,6 @@
+chat.jar:
+% content chat %content/chat/
+	content/chat/browserRequest.js
+	content/chat/browserRequest.xul
+	content/chat/convbrowser.xml
+	content/chat/conv.html
new file mode 100644
--- /dev/null
+++ b/chat/locales/Makefile.in
@@ -0,0 +1,47 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is the Mozilla Browser code.
+#
+# The Initial Developer of the Original Code is
+# Netscape Communications Corporation.
+# Portions created by the Initial Developer are Copyright (C) 2002
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH		= ../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+relativesrcdir	= chat/locales
+
+include $(DEPTH)/config/autoconf.mk
+
+DEFINES += -DAB_CD=$(AB_CD)
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/chat/locales/en-US/accounts.properties
@@ -0,0 +1,5 @@
+# LOCALIZATION NOTE (passwordPromptTitle, passwordPromptText):
+# %S is replaced with the name of the account
+passwordPromptTitle=Password for %S
+passwordPromptText=Please enter your password for %S in order to connect it.
+passwordPromptSaveCheckbox=Use Password Manager to remember this password.
new file mode 100644
--- /dev/null
+++ b/chat/locales/en-US/commands.properties
@@ -0,0 +1,23 @@
+# LOCALIZATION NOTE (commands):
+#  %S is a comma separated list of command names.
+commands=Commands: %S.\nUse /help &lt;command&gt; for more information.
+# LOCALIZATION NOTE (noCommand, noHelp):
+#  %S is the command name the user typed.
+noCommand=No '%S' command.
+noHelp=No help message for the '%S' command, sorry!
+
+sayHelpString=say &lt;message&gt;: send a message without processing commands.
+rawHelpString=raw &lt;message&gt;: send a message without escaping HTML entities.
+helpHelpString=help &lt;name&gt;: show the help message for the &lt;name&gt; command, or the list of possible commands when used without parameter.
+
+# LOCALIZATION NOTE (statusCommand):
+#  %1$S is replaced with a status command name
+#   (one of "back", "away", "busy", "dnd", or "offline").
+#  %2$S is replaced with the localized version of that status type
+#   (one of the 5 strings below).
+statusCommand=%1$S &lt;status message&gt;: set the status to %2$S with an optional status message.
+back=available
+away=away
+busy=unavailable
+dnd=unavailable
+offline=offline
new file mode 100644
--- /dev/null
+++ b/chat/locales/en-US/conversations.properties
@@ -0,0 +1,41 @@
+# LOCALIZATION NOTE (targetChanged):
+#  %1$S is the new conversation title (display name of the new target),
+#  %2$S is the protocol name used for the new target.
+targetChanged=The conversation will continue with %1$S, using %2$S.
+
+# LOCALIZATION NOTE (statusChanged):
+#  %1$S is the display name of the contact.
+#  %2$S is the new status type (a value from status.properties).
+statusChanged=%1$S is now %2$S.
+# LOCALIZATION NOTE (statusChangedWithStatusText):
+#  %1$S is the display name of the contact.
+#  %2$S is the new status type (a value from status.properties).
+#  %3$S is the status text (eg. "I'm currently away from the computer").
+statusChangedWithStatusText=%1$S is now %2$S: %3$S.
+# LOCALIZATION NOTE (statusChangedFromUnknown[WithStatusText]):
+#  special case of the previous 2 strings for when the status was
+#  previously unknown. These 2 strings should not mislead the user
+#  into thinking the person's status has just changed.
+statusChangedFromUnknown=%1$S is %2$S.
+statusChangedFromUnknownWithStatusText=%1$S is %2$S: %3$S.
+
+# LOCALIZATION NOTE (statusUnknown):
+#  %S is the display name of the contact.
+statusUnknown=Your account is disconnected (the status of %S is no longer known).
+accountDisconnected=Your account is disconnected.
+
+# LOCALIZATION NOTE (autoReply):
+#  %S is replaced by the text of a message that was sent as an automatic reply.
+autoReply=Auto-reply - %S
+
+# LOCALIZATION NOTE (messenger.conversations.selections.ellipsis):
+#  ellipsis is used when copying a part of a message to show that the message was cut
+messenger.conversations.selections.ellipsis=[…]
+
+# LOCALIZATION NOTE (messenger.conversations.selections.{system,content,action}MessagesTemplate):
+#  These 3 templates are used to format selected messages before copying them.
+#  Do not translate the texts between % characters, but feel free to adjust
+#  whitespace and separators to make them fit your locale.
+messenger.conversations.selections.systemMessagesTemplate=%time% - %message%
+messenger.conversations.selections.contentMessagesTemplate=%time% - %sender%: %message%
+messenger.conversations.selections.actionMessagesTemplate=%time% * %sender% %message%
new file mode 100644
--- /dev/null
+++ b/chat/locales/en-US/facebook.properties
@@ -0,0 +1,1 @@
+connection.error.useUsernameNotEmailAddress=Please use your Facebook username, not an email address
new file mode 100644
--- /dev/null
+++ b/chat/locales/en-US/irc.properties
@@ -0,0 +1,147 @@
+# LOCALIZATION NOTE (connection.error.*):
+#   These will show in the account manager if the account is
+#   disconnected because of an error.
+connection.error.lost=Lost connection with server
+connection.error.timeOut=Connection timed out
+connection.error.certError=Certification error when connecting to server
+
+# LOCALIZATION NOTE (joinChat.*):
+#   These show up on the join chat menu. An underscore is for the access key.
+joinChat.channel=_Channel
+joinChat.password=_Password
+
+# LOCALIZATION NOTE (options.*):
+#   These are the protocol specific options shown in the account manager and
+#   account wizard windows.
+options.server=Server
+options.port=Port
+options.ssl=Use SSL
+options.encoding=Character Set
+options.quitMessage=Quit message
+options.partMessage=Part message
+options.showServerTab=Show messages from the server
+
+# LOCALIZATION NOTE (ctcp.ping): Semi-colon list of plural forms.
+#  See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+#   %1$S is the nickname of the user who was pinged.
+#   #2 is the delay (in seconds).
+ctcp.ping=Ping reply from %1$S in #2 second.;Ping reply from %1$S in #2 seconds.
+# LOCALIZATION NOTE (ctcp.version):
+#   %1$S is the nickname of the user whose version was requested.
+#   %2$S is the version response from the client.
+ctcp.version=%1$S is using "%2$S"
+# LOCALIZATION NOTE (ctcp.time):
+#   %1$S is the nickname of the user whose time was requested.
+#   %2$S is the time response.
+ctcp.time=The time for %1$S is %2$S.
+
+# LOCALZIATION NOTE (command.*):
+#   These are the help messages for each command, the %S is the command name
+#   Each command first gives the parameter it accepts and then a description of
+#   the command.
+command.action=%S &lt;action to perform&gt;: Perform an action.
+command.ctcp=%S &lt;nick&gt; &lt;msg&gt;: Sends a CTCP message to the nick.
+command.chanserv=%S &lt;command&gt;: Send a command to ChanServ.
+command.deop=%S &lt;nick1&gt;[,&lt;nick2&gt;]*: Remove channel operator status from someone. You must be a channel operator to do this.
+command.devoice=%S &lt;nick1&gt;[,&lt;nick2&gt;]*: Remove channel voice status from someone, preventing them from speaking if the channel is moderated (+m). You must be a channel operator to do this.
+command.invite=%S &lt;nick&gt; [&lt;room&gt;]: Invite someone to join you in the specified channel, or the current channel.
+command.join=%S &lt;room1&gt;[,&lt;room2&gt;]* [&lt;key1&gt;[,&lt;key2&gt;]*]: Enter one or more channels, optionally providing a channel key for each if needed.
+command.kick=%S &lt;nick&gt; [&lt;message&gt;]: Remove someone from a channel. You must be a channel operator to do this.
+command.list=%S: Display a list of chat rooms on the network. Warning, some servers may disconnect you upon doing this.
+command.memoserv=%S &lt;command&gt;: Send a command to MemoServ.
+command.mode=%S (&lt;nick&gt;|&lt;channel&gt;) (+|-)&lt;new mode&gt;: Set or unset a channel or user mode.
+command.msg=%S &lt;nick&gt; &lt;message&gt;: Send a private message to a user (as opposed to a channel).
+command.nick=%S &lt;new nickname&gt;: Change your nickname.
+command.nickserv=%S &lt;command&gt;: Send a command to NickServ.
+command.notice=%S &lt;target&gt; &lt;message&gt;: Send a notice to a user or channel.
+command.op=%S &lt;nick1&gt;[,&lt;nick2&gt;]*: Grant channel operator status to someone. You must be a channel operator to do this.
+command.operserv=%S &lt;command&gt;: Send a command to OperServ.
+command.part=%S [message]: Leave the current channel with an optional message.
+command.ping=%S [&lt;nick&gt;]: Asks how much lag a user (or the server if no user specified) has.
+command.quit=%S &lt;message&gt;: Disconnect from the server, with an optional message.
+command.quote=%S &lt;command&gt;: Send a raw command to the server.
+command.time=%S: Displays the current local time at the IRC server.
+command.topic=%S [&lt;new topic&gt;]: View or change the channel topic.
+command.umode=%S (+|-)&lt;new mode&gt;: Set or unset a user mode.
+command.version=%S &lt;nick&gt;: Request the version of a user's client.
+command.voice=%S &lt;nick1&gt;[,&lt;nick2&gt;]*: Grant channel voice status to someone. You must be a channel operator to do this.
+command.wallops=%S &lt;message&gt;: If you don't know what this is, you probably can't use it (sends a command to all connected with the +w flag and all operators on the server.
+command.whowas=%S &lt;nick&gt;: Get information on a user that has logged off.
+
+# LOCALIZATION NOTE (message.*):
+#    These are shown as system messages in the conversation.
+#    %1$S is the nick and %2$S is the nick and host of the user who joined.
+message.join=%1$S [%2$S] entered the room.
+#    %1$S is the nick of who kicked you.
+#    %2$S is message.kicked.reason, if a kick message was given.
+message.kicked.you=You have been kicked by %1$S%2$S.
+#    %1$S is the nick that is kicked, %2$S the nick of the person who kicked
+#    %1$S. %3$S is message.kicked.reason, if a kick message was given.
+message.kicked=%1$S has been kicked by %2$S%3$S.
+#    %S is the kick message
+message.kicked.reason=: %S
+#    %1$S is the nickname of the user whose mode was changed, %2$S is the new
+#    mode and %3$S is who set the mode.
+message.mode=mode (%1$S %2$S) by %3$S.
+#    %1$S is the old nick and %2$S is the new nick.
+message.nick=%1$S is now known as %2$S.
+#    %S is your new nick.
+message.nick.you=You are now known as %S.
+#    The paramter is the message.parted.reason, if a part message is given.
+message.parted.you=You have left the room (Part%1$S).
+#    %1$S is the user's nick, %2$S is message.parted.reason, if a part message is given.
+message.parted=%1$S has left the room (Part%2$S).
+#    %S is the part message supplied by the user.
+message.parted.reason=: %S
+#    %1$S is the user's nick, %2$S is message.quit2 if a quit message is given.
+message.quit=%1$S has left the room (Quit%2$S).
+#    The paramter is the quit message given by the user.
+message.quit2=: %S
+#    %1$S is the user who changed the topic, %2$S is the new topic.
+message.topicChanged=%1$S has changed the topic to: %2$S.
+#    %1$S is the user who cleared the topic.
+message.topicCleared=%1$S has cleared the topic.
+#    %1$S is the conversation name, %2$S is the topic.
+message.topic=The topic for %1$S is: %2$S.
+#    %S is the conversation name.
+message.topicRemoved=The topic for %S was removed.
+#    %1$S is the nickname of the invited user, %2$S is the conversation name
+#    they were invited to.
+message.invited=%1$S was successfully invited to %2$S.
+#    %S is the nickname of the user who was summoned.
+message.summoned=%S was summoned.
+
+# LOCALIZATION NOTE (error.*):
+#    These are shown as error messages in the conversation.
+#    %S is the channel name.
+error.noChannel=There is no channel: %S.
+error.tooManyChannels=Cannot join %S; you've joined too many channels.
+#    %1$S is your new nick, %2$S is the kill message from the server.
+error.nickCollision=Nick already in use, changing nick to %1$S [%2$S].
+error.banned=You are banned from this server.
+error.bannedSoon=You will soon be banned from this server.
+error.mode.wrongUser=You cannot change modes for other users.
+
+# LOCALIZATION NOTE (tooltip.*):
+#    These are the descriptions given in a tooltip with information received
+#    from a whois response.
+#    The human readable ("realname") description of the user.
+tooltip.realname=Name
+#    The username and hostname that the user connects from (usually based on the
+#    reverse DNS of the user's IP, but often mangled by the server to
+#    protect users).
+tooltip.connectedFrom=Connected from
+# The away message of the user
+tooltip.away=Away
+tooltip.ircOp=IRC Operator
+tooltip.channels=Currently on
+tooltip.server=Connected to
+#    %1$S is the server name, %2$S is the server location.
+tooltip.serverValue=%1$S (%2$S)
+tooltip.idleTime=Idle for
+
+# LOCALIZATION NOTE (gtalk.usernameHint):
+#  This is displayed inside the accountUsernameInfoWithDescription
+#  string defined in imAccounts.properties when the user is
+#  configuring an IRC account.
+usernameHint=and server
new file mode 100644
--- /dev/null
+++ b/chat/locales/en-US/status.properties
@@ -0,0 +1,14 @@
+availableStatusType=Available
+awayStatusType=Away
+unavailableStatusType=Unavailable
+offlineStatusType=Offline
+invisibleStatusType=Invisible
+idleStatusType=Idle
+mobileStatusType=Mobile
+# LOCALIZATION NOTE (unknownStatusType):
+# the status of a buddy is unknown when it's in the list of a disconnected account
+unknownStatusType=Unknown
+
+# LOCALIZATION NOTE (messenger.status.defaultIdleAwayMessage):
+#  This will be the away message put automatically when the user is idle.
+messenger.status.defaultIdleAwayMessage=I am currently away from the computer.
new file mode 100644
--- /dev/null
+++ b/chat/locales/en-US/twitter.properties
@@ -0,0 +1,101 @@
+# LOCALIZATION NOTE (error.*):
+#   These are errors that will be shown to the user in conversation.
+error.tooLong=Status is over 140 characters.
+# LOCALIZATION NOTE (error.general, error.retweet, error.delete):
+#   %1$S will be either the error string returned by the twitter server,
+#   in English, inside parenthesis, or the empty string if we have no specific
+#   message for the error.
+#   %2$S is the message that caused the error.
+error.general=An error %1$S occurred while sending: %2$S
+error.retweet=An error %1$S occurred while retweeting: %2$S
+error.delete=An error %1$S occurred while deleting: %2$S
+
+# LOCALIZATION NOTE (timeline):
+#   This is the title of the conversation tab, %S will be replaced by
+#   @<username>.
+timeline=%S timeline
+
+# LOCALIZATION NOTE (action.*):
+#  This will be an action in the context menu of displayed tweets.
+action.copyLink=Copy Link to Tweet
+action.retweet=Retweet
+action.reply=Reply
+action.delete=Delete
+# LOCALIZATION NOTE (action.follow, action.stopFollowing):
+#  %S will be replaced by the screen name of a twitter user.
+action.follow=Follow %S
+action.stopFollowing=Stop following %S
+
+# LOCALIZATION NOTE (event.follow, event.unfollow, event.followed):
+#  This will be displayed in system messages inside the timeline conversation.
+#  %S will be replaced by the screen name of a twitter user.
+event.follow=You are now following %S.
+event.unfollow=You are no longer following %S.
+event.followed=%S is now following you.
+# LOCALIZATION NOTE (event.deleted):
+#  %S will be replaced by the text of the deleted tweet.
+event.deleted=You have deleted this tweet: "%S".
+
+# LOCALIZATION NOTE (replyingToStatusText):
+#  This will be visible in the status bar of the conversation window
+#  while the user is typing a reply to a tweet.
+#  %S will be replaced by the text of the tweet the user is replying to.
+replyingToStatusText=Replying to: %S
+
+# LOCALIZATION NOTE (connection.*):
+#   These will be displayed in the account manager in order to show the progress
+#   of the connection.
+#   (These will be displayed in account.connection.progress from
+#    accounts.properties, which adds … at the end, so do not include
+#    periods at the end of these messages.)
+connection.initAuth=Initiating authentication process
+connection.requestAuth=Waiting for your authorization
+connection.requestAccess=Finalizing authentication
+connection.requestTimelines=Requesting user timelines
+# LOCALIZATION NOTE (connection.error.*):
+#   These will show in the account manager if an error occurs during the
+#   connection attempt.
+connection.error.userMismatch=Username mismatch.
+connection.error.failedToken=Failed to get request token.
+connection.error.authCancelled=You cancelled the authorization process.
+connection.error.authFailed=Failed to get authorization.
+connection.error.noNetwork=There is no network connection available.
+
+# LOCALIZATION NOTE (authPrompt):
+#   This is the prompt in the browser window that pops up to authorize us
+#   to use a Twitter account. It is shown in the title bar of the authorization
+#   window.
+authPrompt=Give permission to use your Twitter account
+
+# LOCALIZATION NOTE (options.*):
+#   These are the protocol specific options shown in the account manager and
+#   account wizard windows.
+options.track=Tracked keywords
+
+# LOCALIZATION NOTE (tooltip.*):
+#   These are the Twitter information that will appear in the tooltip
+#   for each participant on the home timeline.
+# LOCALIZATION NOTE (tooltip.created_at): the date the user joined.
+tooltip.created_at=User Since
+tooltip.location=Location
+tooltip.lang=Language
+tooltip.time_zone=Timezone
+tooltip.url=Homepage
+# LOCALIZATION NOTE (tooltip.protected):
+#  whether the user's tweets are publicly visible.
+tooltip.protected=Protects Tweets
+# LOCALIZATION NOTE (tooltip.following):
+#  whether you are subscribed to the user's tweets.
+tooltip.following=Currently Following
+tooltip.description=Description
+# LOCALIZATION NOTE (tooltip.*_count):
+#  Please see the right side of the official Twitter website UI.
+tooltip.friends_count=Following
+tooltip.statuses_count=Tweets
+tooltip.followers_count=Followers
+tooltip.listed_count=Listed
+
+# LOCALIZATION NOTE (yes, no):
+#  These are used to turn true/false values into a yes/no response.
+yes=Yes
+no=No
new file mode 100644
--- /dev/null
+++ b/chat/locales/en-US/xmpp.properties
@@ -0,0 +1,72 @@
+# LOCALIZATION NOTE (connection.*)
+#   These will be displayed in the account manager in order to show the progress
+#   of the connection.
+#   (These will be displayed in account.connection.progress from
+#    accounts.properties, which adds … at the end, so do not include
+#    periods at the end of these messages.)
+connection.initializingStream=Initializing stream
+connection.initializingEncryption=Initializing encryption
+connection.authenticating=Authenticating
+connection.gettingResource=Getting resource
+connection.downloadingRoster=Downloading contact list
+
+# LOCALIZATION NOTE (connection.error.*)
+#   These will show in the account manager if an error occurs during the
+#   connection attempt.
+connection.error.failedToCreateASocket=Failed to create a socket (Are you offline?)
+connection.error.serverClosedConnection=The server closed the connection
+connection.error.resetByPeer=Connection reset by peer
+connection.error.timedOut=The connection timed out
+connection.error.receivedUnexpectedData=Received unexpected data
+connection.error.incorrectResponse=Received an incorrect response
+connection.error.startTLSRequired=The server requires encryption but you disabled it
+connection.error.startTLSNotSupported=The server doesn't support encryption but your configuration requires it
+connection.error.failedToStartTLS=Failed to start encryption
+connection.error.noAuthMec=No authentication mechanism offered by the server
+connection.error.noCompatibleAuthMec=None of the authentication mechanisms offered by the server are supported
+connection.error.notSendingPasswordInClear=The server only supports authentication by sending the password in cleartext
+connection.error.authenticationFailure=Authentication failure
+connection.error.notAuthorized=Not authorized (Did you enter the wrong password?)
+connection.error.failedToGetAResource=Failed to get a resource
+
+# LOCALIZATION NOTE (tooltip.*):
+#   These are the titles of lines of information that will appear in
+#   the tooltip showing details about a contact or conversation.
+# LOCALIZATION NOTE (tooltip.status):
+#   %S will be replaced by the XMPP resource identifier
+tooltip.status=Status (%S)
+tooltip.statusNoResource=Status
+tooltip.subscription=Subscription
+
+# LOCALIZATION NOTE (chatRoomField.*):
+#   These are the name of fields displayed in the 'Join Chat' dialog
+#   for XMPP accounts.
+#   The _ character won't be displayed; it indicates the next
+#   character of the string should be used as the access key for this
+#   field.
+chatRoomField.room=_Room
+chatRoomField.server=_Server
+chatRoomField.nick=_Nick
+chatRoomField.password=_Password
+
+# LOCALIZATION NOTE
+#  Buddies that aren't in any group on the server will appear in this group.
+#  Try to use the same translation as for defaultGroup in instantbird.properties
+defaultGroup=Contacts
+
+# LOCALIZATION NOTE (options.*):
+#   These are the protocol specific options shown in the account manager and
+#   account wizard windows.
+options.resource=Resource
+options.connectionSecurity=Connection security
+options.connectionSecurity.requireEncryption=Require encryption
+options.connectionSecurity.opportunisticTLS=Use encryption if available
+options.connectionSecurity.allowUnencryptedAuth=Allow sending the password unencrypted
+options.connectServer=Server
+options.connectPort=Port
+
+# LOCALIZATION NOTE (gtalk.usernameHint):
+#  This is displayed inside the accountUsernameInfoWithDescription
+#  string defined in imAccounts.properties when the user is
+#  configuring a Google Talk account.
+gtalk.usernameHint=email address
new file mode 100644
--- /dev/null
+++ b/chat/locales/jar.mn
@@ -0,0 +1,12 @@
+#filter substitution
+
+@AB_CD@.jar:
+% locale chat @AB_CD@ %locale/@AB_CD@/chat/
+	locale/@AB_CD@/chat/accounts.properties (%accounts.properties)
+	locale/@AB_CD@/chat/commands.properties (%commands.properties)
+	locale/@AB_CD@/chat/conversations.properties (%conversations.properties)
+	locale/@AB_CD@/chat/facebook.properties	(%facebook.properties)
+	locale/@AB_CD@/chat/irc.properties	(%irc.properties)
+	locale/@AB_CD@/chat/status.properties	(%status.properties)
+	locale/@AB_CD@/chat/twitter.properties	(%twitter.properties)
+	locale/@AB_CD@/chat/xmpp.properties	(%xmpp.properties)
new file mode 100644
--- /dev/null
+++ b/chat/makefiles.sh
@@ -0,0 +1,51 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is the Mozilla build system.
+#
+# The Initial Developer of the Original Code is
+#  Florian Queze <florian@instantbird.org>
+# Portions created by the Initial Developer are Copyright (C) 2008
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+add_makefiles "
+chat/Makefile
+chat/components/public/Makefile
+chat/components/src/Makefile
+chat/content/Makefile
+chat/locales/Makefile
+chat/modules/Makefile
+chat/protocols/facebook/Makefile
+chat/protocols/gtalk/Makefile
+chat/protocols/irc/Makefile
+chat/protocols/jsTest/Makefile
+chat/protocols/twitter/Makefile
+chat/protocols/xmpp/Makefile
+chat/themes/Makefile
+"
new file mode 100644
--- /dev/null
+++ b/chat/modules/Makefile.in
@@ -0,0 +1,61 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is
+# Netscape Communications Corporation.
+# Portions created by the Initial Developer are Copyright (C) 1998
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either of the GNU General Public License Version 2 or later (the "GPL"),
+# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH		= ../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+EXTRA_JS_MODULES = \
+	http.jsm \
+	imContentSink.jsm \
+	imServices.jsm \
+	imSmileys.jsm \
+	imStatusUtils.jsm \
+	imThemes.jsm \
+	imXPCOMUtils.jsm \
+	jsProtoHelper.jsm \
+	socket.jsm \
+	$(NULL)
+
+EXTRA_PP_JS_MODULES = \
+	hiddenWindow.jsm \
+	imTextboxUtils.jsm \
+	$(NULL)
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/chat/modules/hiddenWindow.jsm
@@ -0,0 +1,54 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2011.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const EXPORTED_SYMBOLS = ["getHiddenHTMLWindow"];
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "hiddenWindow", function()
+  Components.classes["@mozilla.org/appshell/appShellService;1"]
+                      .getService(Components.interfaces.nsIAppShellService)
+                      .hiddenDOMWindow
+);
+#ifndef XP_MACOSX
+function getHiddenHTMLWindow() hiddenWindow
+#else
+function getHiddenHTMLWindow() {
+  let browser = hiddenWindow.document.getElementById("hiddenBrowser");
+  return browser.docShell ? browser.contentWindow : hiddenWindow;
+}
+#endif
new file mode 100644
--- /dev/null
+++ b/chat/modules/http.jsm
@@ -0,0 +1,108 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2010.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const EXPORTED_SYMBOLS = ["doXHRequest"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource:///modules/imXPCOMUtils.jsm");
+
+initLogModule("xhr", this);
+
+function doXHRequest(aUrl, aHeaders, aPOSTData, aOnLoad, aOnError, aThis) {
+  let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+              .createInstance(Ci.nsIXMLHttpRequest);
+  xhr.mozBackgroundRequest = true; // no error dialogs
+  xhr.open(aPOSTData ? "POST" : "GET", aUrl);
+  xhr.channel.loadFlags = Ci.nsIChannel.LOAD_ANONYMOUS | // don't send cookies
+                          Ci.nsIChannel.LOAD_BYPASS_CACHE |
+                          Ci.nsIChannel.INHIBIT_CACHING;
+  xhr.onerror = function(aProgressEvent) {
+    if (aOnError) {
+      // adapted from toolkit/mozapps/extensions/nsBlocklistService.js
+      let request = aProgressEvent.target;
+      let status;
+      try {
+        // may throw (local file or timeout)
+        status = request.status;
+      }
+      catch (e) {
+        request = request.channel.QueryInterface(Ci.nsIRequest);
+        status = request.status;
+      }
+      // When status is 0 we don't have a valid channel.
+      let statusText = status ? request.statusText : "offline";
+      aOnError.call(aThis, statusText, null, this);
+    }
+  };
+  xhr.onload = function (aRequest) {
+    try {
+      let target = aRequest.target;
+      DEBUG("Received response: " + target.responseText);
+      if (target.status != 200) {
+        let errorText = target.responseText;
+        if (!errorText || /<(ht|\?x)ml\b/i.test(errorText))
+          errorText = target.statusText;
+        throw target.status + " - " + errorText;
+      }
+      if (aOnLoad)
+        aOnLoad.call(aThis, target.responseText, this);
+    } catch (e) {
+      Cu.reportError(e);
+      if (aOnError)
+        aOnError.call(aThis, e, aRequest.target.responseText, this);
+    }
+  };
+
+  if (aHeaders) {
+    aHeaders.forEach(function(header) {
+      xhr.setRequestHeader(header[0], header[1]);
+    });
+  }
+
+  let POSTData = "";
+  if (aPOSTData) {
+    xhr.setRequestHeader("Content-Type",
+                         "application/x-www-form-urlencoded; charset=utf-8");
+    POSTData = aPOSTData.map(function(p) p[0] + "=" + encodeURIComponent(p[1]))
+                        .join("&");
+  }
+
+  LOG("sending request to " + aUrl + " (POSTData = " + POSTData + ")");
+  xhr.send(POSTData);
+  return xhr;
+}
new file mode 100644
--- /dev/null
+++ b/chat/modules/imContentSink.jsm
@@ -0,0 +1,375 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2009.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+Components.utils.import("resource:///modules/imServices.jsm");
+
+const EXPORTED_SYMBOLS = [
+  "cleanupImMarkup", // used to clean up incoming IMs.
+                     // This will use the global ruleset of acceptable stuff
+                     // except if another (custom one) is provided
+  "createDerivedRuleset", // used to create a ruleset that inherits from the
+                          // default one
+                          // useful if you want to allow or forbid
+                          // an additionnal thing in a specific
+                          // conversation but take into account all
+                          // the other global settings.
+  "addGlobalAllowedTag",
+  "removeGlobalAllowedTag",
+  "addGlobalAllowedAttribute",
+  "removeGlobalAllowedAttribute",
+  "addGlobalAllowedStyleRule",
+  "removeGlobalAllowedStyleRule"
+];
+
+/*
+ * Structure of a ruleset:
+ * A ruleset is a JS object containing 3 sub-objects: attrs, tags and styles.
+ *  - attrs: an object containing a list of attributes allowed for all tags.
+ *      example: attrs: { 'style': true }
+ *
+ *  - tags: an object with the allowed tags. each tag can allow specific attributes.
+ *      example: 'a': {'href': true}
+ *
+ *    each attribute can have a function returning a boolean indicating if
+ *    the attribute is accepted.
+ *      example: 'href': function(aValue) aValue == 'about:blank'
+ *
+ *  - styles: an object with the allowed CSS style rule.
+ *      example: 'font-size': true
+ *    FIXME: make this accept functions to filter the CSS values too.
+ *
+ *  See the 3 examples of rulesets below.
+ */
+
+const kAllowedURLs = function(aValue) /^(https?|ftp|mailto):/.test(aValue);
+const kAllowedMozClasses =
+  function(aClassName) aClassName == "moz-txt-underscore" ||
+                       aClassName == "moz-txt-tag";
+
+// in strict mode, remove all formatings. Keep only links and line breaks.
+const kStrictMode = {
+  attrs: { },
+
+  tags: {
+    'a': {
+      'title': true,
+      'href': kAllowedURLs
+    },
+    'br': true,
+    'p': true
+  },
+
+  styles: { }
+};
+
+// standard mode allows basic formattings (bold, italic, underlined)
+const kStandardMode = {
+  attrs: {
+    'style': true
+  },
+
+  tags: {
+    'div': true,
+    'a': {
+      'title': true,
+      'href': kAllowedURLs
+    },
+    'em': true,
+    'strong': true,
+    'b': true,
+    'i': true,
+    'u': true,
+    'span': {
+      'class': kAllowedMozClasses
+    },
+    'br': true,
+    'code': true,
+    'ul': true,
+    'li': true,
+    'ol': true,
+    'cite': true,
+    'blockquote': true,
+    'p': true
+  },
+
+  styles: {
+    'font-style': true,
+    'font-weight': true,
+    'text-decoration': true
+  }
+};
+
+// permissive mode allows about anything that isn't going to mess up the chat window
+const kPermissiveMode = {
+  attrs: {
+    'style': true
+  },
+
+  tags : {
+    'div': true,
+    'a': {
+      'title': true,
+      'href': kAllowedURLs
+    },
+    'font': {
+      'face': true,
+      'color': true,
+      'size': true
+    },
+    'em': true,
+    'strong': true,
+    'b': true,
+    'i': true,
+    'u': true,
+    'span': {
+      'class': kAllowedMozClasses
+    },
+    'br': true,
+    'hr': true,
+    'code': true,
+    'ul': true,
+    'li': true,
+    'ol': true,
+    'cite': true,
+    'blockquote': true,
+    'p': true
+  },
+
+  // FIXME: should be possible to use functions to filter values
+  styles : {
+    'color': true,
+    'font': true,
+    'font-family': true,
+    'font-size': true,
+    'font-style': true,
+    'font-weight': true,
+    'text-decoration': true
+  }
+};
+
+const kModePref = "messenger.options.filterMode";
+const kModes = [kStrictMode, kStandardMode, kPermissiveMode];
+
+var gGlobalRuleset = null;
+
+function initGlobalRuleset()
+{
+  gGlobalRuleset = newRuleset();
+
+  Services.prefs.addObserver(kModePref, styleObserver, false);
+}
+
+var styleObserver = {
+  observe: function so_observe(aObject, aTopic, aMsg) {
+    if (aTopic != "nsPref:changed" || aMsg != kModePref)
+      throw "bad notification";
+
+    if (!gGlobalRuleset)
+      throw "gGlobalRuleset not initialized";
+
+    setBaseRuleset(getModePref(), gGlobalRuleset);
+  }
+};
+
+function getModePref()
+{
+  let baseNum = Services.prefs.getIntPref(kModePref);
+  if (baseNum < 0 || baseNum > 2)
+    baseNum = 1;
+
+  return kModes[baseNum];
+}
+
+function setBaseRuleset(aBase, aResult)
+{
+  aResult.tags.__proto__ = aBase.tags;
+  aResult.attrs.__proto__ = aBase.attrs;
+  aResult.styles.__proto__ = aBase.styles;
+}
+
+function newRuleset(aBase)
+{
+  if (!aBase)
+    aBase = getModePref();
+
+  let result = {};
+  result.tags = {};
+  result.attrs = {};
+  result.styles = {};
+  setBaseRuleset(aBase, result);
+  return result;
+}
+
+function createDerivedRuleset()
+{
+  if (!gGlobalRuleset)
+    initGlobalRuleset();
+  return newRuleset(gGlobalRuleset);
+}
+
+function addGlobalAllowedTag(aTag, aAttrs)
+{
+  gGlobalRuleset.tags[aTag] = aAttrs || true;
+}
+function removeGlobalAllowedTag(aTag)
+{
+  delete gGlobalRuleset.tags[aTag];
+}
+
+function addGlobalAllowedAttribute(aAttr, aRule)
+{
+  gGlobalRuleset.attrs[aAttr] = aRule || true;
+}
+function removeGlobalAllowedAttribute(aAttr)
+{
+  delete gGlobalRuleset.attrs[aAttr];
+}
+
+function addGlobalAllowedStyleRule(aStyle, aRule)
+{
+  gGlobalRuleset.styles[aStyle] = aRule || true;
+}
+function removeGlobalAllowedStyleRule(aStyle)
+{
+  delete gGlobalRuleset.styles[aStyle];
+}
+
+function cleanupNode(aNode, aRules, aTextModifiers)
+{
+  for (let i = 0; i < aNode.childNodes.length; ++i) {
+    let node = aNode.childNodes[i];
+    if (node instanceof Components.interfaces.nsIDOMHTMLElement) {
+      // check if node allowed
+      let nodeName = node.localName.toLowerCase();
+      if (!(nodeName in aRules.tags)) {
+        // this node is not allowed, replace it with its children
+        while (node.hasChildNodes())
+          aNode.insertBefore(node.removeChild(node.firstChild), node);
+        aNode.removeChild(node);
+        // We want to process again the node at the index i which is
+        // now the first child of the node we removed
+        --i;
+        continue;
+      }
+
+      // we are going to keep this child node, clean up its children
+      cleanupNode(node, aRules, aTextModifiers);
+
+      // cleanup attributes
+      let attrs = node.attributes;
+      let acceptFunction = function(aAttrRules, aAttr) {
+        // an attribute is always accepted if its rule is true, or conditionnaly
+        // accepted if its rule is a function that evaluates to true
+        // if its rule does not exist, it is refused
+          let localName = aAttr.localName;
+          let rule = localName in aAttrRules && aAttrRules[localName];
+          return (rule === true ||
+                  (rule instanceof Function && rule(aAttr.value)));
+      };
+      for (let j = 0; j < attrs.length; ++j) {
+        let attr = attrs[j];
+        // we check both the list of accepted attributes for all tags
+        // and the list of accepted attributes for this specific tag.
+        if (!(acceptFunction(aRules.attrs, attr) ||
+              (aRules.tags[nodeName] instanceof Object) &&
+              acceptFunction(aRules.tags[nodeName], attr))) {
+          node.removeAttribute(attr.name);
+          --j;
+        }
+      }
+
+      // cleanup style
+      let style = node.style;
+      for (let j = 0; j < style.length; ++j) {
+        if (!(style[j] in aRules.styles)) {
+          style.removeProperty(style[j]);
+          --j;
+        }
+      }
+    }
+    else {
+      // We are on a text node, we need to apply the functions
+      // provided in the aTextModifiers array.
+
+      // Each of these function should return the number of nodes added:
+      //  * -1 if the current textnode was deleted
+      //  * 0 if the node count is unchanged
+      //  * positive value if nodes were added.
+      //     For instance, adding an <img> tag for a smiley adds 2 nodes:
+      //      - the img tag
+      //      - the new text node after the img tag.
+
+      // This is the number of nodes we need to process. If new nodes
+      // are created, the next text modifier functions have more nodes
+      // to process.
+      let textNodeCount = 1;
+      for each (let modifier in aTextModifiers)
+        for (let n = 0; n < textNodeCount; ++n) {
+          let textNode = aNode.childNodes[i + n];
+
+          // If we are processing nodes created by one of the previous
+          // text modifier function, some of the nodes are likely not
+          // text node, skip them.
+          if (!(textNode instanceof Components.interfaces.nsIDOMText))
+            continue;
+
+          let result = modifier(textNode);
+          textNodeCount += result;
+          n += result;
+        }
+
+      // newly created nodes should not be filtered, be sure we skip them!
+      i += textNodeCount - 1;
+    }
+  }
+}
+
+function cleanupImMarkup(aDocument, aText, aRuleset, aTextModifiers)
+{
+  if (!aDocument)
+    throw "providing an HTML document is required";
+
+  if (!gGlobalRuleset)
+    initGlobalRuleset();
+
+  let parser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
+                         .createInstance(Components.interfaces.nsIDOMParser);
+  let doc = parser.parseFromString(aText, "text/html");
+  let div = doc.getElementsByTagName("body")[0];
+  cleanupNode(div, aRuleset || gGlobalRuleset, aTextModifiers || []);
+  return div.innerHTML;
+}
new file mode 100644
--- /dev/null
+++ b/chat/modules/imServices.jsm
@@ -0,0 +1,63 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2010.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const EXPORTED_SYMBOLS = ["Services"];
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(Services, "accounts",
+                                   "@mozilla.org/chat/accounts-service;1",
+                                   "imIAccountsService");
+XPCOMUtils.defineLazyServiceGetter(Services, "core",
+                                   "@mozilla.org/chat/core-service;1",
+                                   "imICoreService");
+XPCOMUtils.defineLazyServiceGetter(Services, "cmd",
+                                   "@mozilla.org/chat/commands-service;1",
+                                   "imICommandsService");
+XPCOMUtils.defineLazyServiceGetter(Services, "contacts",
+                                   "@mozilla.org/chat/contacts-service;1",
+                                   "imIContactsService");
+XPCOMUtils.defineLazyServiceGetter(Services, "conversations",
+                                   "@mozilla.org/chat/conversations-service;1",
+                                   "imIConversationsService");
+XPCOMUtils.defineLazyServiceGetter(Services, "tags",
+                                   "@mozilla.org/chat/tags-service;1",
+                                   "imITagsService");
+XPCOMUtils.defineLazyServiceGetter(Services, "logs",
+                                   "@mozilla.org/chat/logger;1",
+                                   "imILogger");
new file mode 100644
--- /dev/null
+++ b/chat/modules/imSmileys.jsm
@@ -0,0 +1,249 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2009.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+Components.utils.import("resource:///modules/imServices.jsm");
+
+const EXPORTED_SYMBOLS = [
+  "smileImMarkup", // used to add smile:// img tags into IM markup.
+  "smileTextNode", // used to add smile:// img tags to the content of a textnode
+  "smileString", // used to add smile:// img tags into a string without parsing it as HTML. Be sure the string doesn't contain HTML tags.
+  "getSmileRealURI", // used to retrive the chrome URI for a smile:// URI
+  "getSmileyList" // used to display a list of smileys in the UI
+];
+
+const kEmoticonsThemePref = "messenger.options.emoticonsTheme";
+const kThemeFile = "theme.js";
+
+__defineGetter__("gTheme", function() {
+  delete this.gTheme;
+  gPrefObserver.init();
+  return this.gTheme = getTheme();
+});
+
+var gPrefObserver = {
+  init: function po_init() {
+    Services.prefs.addObserver(kEmoticonsThemePref, gPrefObserver, false);
+  },
+
+  observe: function so_observe(aObject, aTopic, aMsg) {
+    if (aTopic != "nsPref:changed" || aMsg != kEmoticonsThemePref)
+      throw "bad notification";
+
+    gTheme = getTheme();
+  }
+};
+
+function getSmileRealURI(aSmile)
+{
+  aSmile = Components.classes["@mozilla.org/intl/texttosuburi;1"]
+                     .getService(Components.interfaces.nsITextToSubURI)
+                     .unEscapeURIForUI("UTF-8", aSmile);
+  if (aSmile in gTheme.iconsHash)
+    return gTheme.baseUri + gTheme.iconsHash[aSmile].filename;
+
+  throw "Invalid smile!";
+}
+
+function getSmileyList(aThemeName)
+{
+  let theme = aThemeName == gTheme.name ? gTheme : getTheme(aThemeName);
+  if (!theme.json)
+    return null;
+
+  let addAbsoluteUrls = function(aSmiley) {
+    return {filename: aSmiley.filename,
+            src: theme.baseUri + aSmiley.filename,
+            textCodes: aSmiley.textCodes};
+  };
+  return theme.json.smileys.map(addAbsoluteUrls);
+}
+
+function getTheme(aName)
+{
+  let name = aName || Services.prefs.getCharPref(kEmoticonsThemePref);
+
+  let theme = {
+    name: name,
+    iconsHash: null,
+    json: null,
+    regExp: null
+  };
+
+  if (name == "none")
+    return theme;
+
+  if (name == "default")
+    theme.baseUri = "chrome://instantbird-emoticons/skin/";
+  else
+    theme.baseUri = "chrome://" + theme.name + "/skin/";
+  try {
+    let channel = Services.io.newChannel(theme.baseUri + kThemeFile, null, null);
+    let stream = channel.open();
+    let json = Components.classes["@mozilla.org/dom/json;1"]
+                         .createInstance(Components.interfaces.nsIJSON);
+    theme.json = json.decodeFromStream(stream, stream.available());
+    stream.close();
+    theme.iconsHash = {};
+    for each (let smiley in theme.json.smileys) {
+      for each (let textCode in smiley.textCodes)
+        theme.iconsHash[textCode] = smiley;
+    }
+  } catch(e) {
+    Components.utils.reportError(e);
+  }
+  return theme;
+}
+
+function getRegexp()
+{
+  if (gTheme.regExp) {
+    gTheme.regExp.lastIndex = 0;
+    return gTheme.regExp;
+  }
+
+  // return null if smileys are disabled
+  if (!gTheme.iconsHash)
+    return null;
+
+  if ("" in gTheme.iconsHash) {
+    Components.utils.reportError("Emoticon " +
+                                 gTheme.iconsHash[""].filename +
+                                 " matches the empty string!");
+    delete gTheme.iconsHash[""];
+  }
+
+  let emoticonList = [];
+  for (let emoticon in gTheme.iconsHash)
+    emoticonList.push(emoticon);
+
+  let exp = /([\][)(\\|?^$*+])/g;
+  emoticonList = emoticonList.sort()
+                             .reverse()
+                             .map(function(x) x.replace(exp, "\\$1"));
+
+  if (!emoticonList.length) {
+    // the theme contains no valid emoticon, make sure we will return
+    // early next time
+    gTheme.iconsHash = null;
+    return null;
+  }
+
+  gTheme.regExp = new RegExp('(' + emoticonList.join('|') + ')', 'g');
+  return gTheme.regExp;
+}
+
+// unused. May be useful later to process a string instead of an HTML node
+function smileString(aString)
+{
+  const kSmileFormat = '<img class="ib-img-smile" src="smile://$1" alt="$1" title="$1"/>';
+
+  let exp = getRegexp();
+  return exp ? aString.replace(exp, kSmileFormat) : aString;
+}
+
+function smileTextNode(aNode)
+{
+  /*
+   * Skip text nodes that contain the href in the child text node.
+   * We must check both the testNode.textContent and the aNode.data since they
+   * cover different cases:
+   *   textContent: The URL is split over multiple nodes for some reason
+   *   data: The URL is not the only content in the link, skip only the one node
+   * Check the class name to skip any autolinked nodes from mozTXTToHTMLConv.
+   */
+  let testNode = aNode;
+  while ((testNode = testNode.parentNode)) {
+    if (testNode instanceof Components.interfaces.nsIDOMHTMLAnchorElement &&
+        (testNode.getAttribute("href") == testNode.textContent.trim() ||
+         testNode.getAttribute("href") == aNode.data.trim() ||
+         testNode.className.indexOf("moz-txt-link-") != -1))
+      return 0;
+  }
+
+  let result = 0;
+  let exp = getRegexp();
+  if (!exp)
+    return result;
+
+  let match;
+  while ((match = exp.exec(aNode.data))) {
+    let smileNode = aNode.splitText(match.index);
+    aNode = smileNode.splitText(exp.lastIndex - match.index);
+    // at this point, smileNode is a text node with only the text
+    // of the smiley and aNode is a text node with the text after
+    // the smiley. The text in aNode hasn't been processed yet.
+    let smile = smileNode.data;
+    let elt = aNode.ownerDocument.createElement("img");
+    elt.setAttribute("src", "smile://" + smile);
+    elt.setAttribute("title", smile);
+    elt.setAttribute("alt", smile);
+    elt.setAttribute("class", "ib-img-smile");
+    smileNode.parentNode.replaceChild(elt, smileNode);
+    result += 2;
+    exp.lastIndex = 0;
+  }
+  return result;
+}
+
+function smileNode(aNode)
+{
+  for (let i = 0; i < aNode.childNodes.length; ++i) {
+    let node = aNode.childNodes[i];
+    if (node instanceof Components.interfaces.nsIDOMHTMLElement) {
+      // we are on a tag, recurse to process its children
+      smileNode(node);
+    } else if (node instanceof Components.interfaces.nsIDOMText) {
+      // we are on a text node, process it
+      smileTextNode(node);
+    }
+  }
+}
+
+function smileImMarkup(aDocument, aText)
+{
+  if (!aDocument)
+    throw "providing an HTML document is required";
+
+  // return early if smileys are disabled
+  if (!gTheme.iconsHash)
+    return aText;
+
+  let div = aDocument.createElement("div");
+  div.innerHTML = aText;
+  smileNode(div);
+  return div.innerHTML;
+}
new file mode 100644
--- /dev/null
+++ b/chat/modules/imStatusUtils.jsm
@@ -0,0 +1,74 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const EXPORTED_SYMBOLS = ["Status"];
+
+Components.utils.import("resource:///modules/imServices.jsm");
+
+const imIStatusInfo = Components.interfaces.imIStatusInfo;
+let statusNames = {};
+statusNames[imIStatusInfo.STATUS_UNKNOWN] = "unknown";
+statusNames[imIStatusInfo.STATUS_OFFLINE] = "offline";
+statusNames[imIStatusInfo.STATUS_INVISIBLE] = "invisible";
+statusNames[imIStatusInfo.STATUS_MOBILE] = "mobile";
+statusNames[imIStatusInfo.STATUS_IDLE] = "idle";
+statusNames[imIStatusInfo.STATUS_AWAY] = "away";
+statusNames[imIStatusInfo.STATUS_UNAVAILABLE] = "unavailable";
+statusNames[imIStatusInfo.STATUS_AVAILABLE] = "available";
+
+const Status = {
+  toAttribute: function(aStatusType)
+    aStatusType in statusNames ? statusNames[aStatusType] : "unknown",
+
+  _labels: {},
+  toLabel: function(aStatusType) {
+    if (!(typeof aStatusType == "string"))
+      aStatusType = this.toAttribute(aStatusType);
+
+    if (!(aStatusType in this._labels)) {
+      this._labels[aStatusType] =
+        Services.strings.createBundle("chrome://chat/locale/status.properties")
+                .GetStringFromName(aStatusType + "StatusType");
+    }
+    return this._labels[aStatusType];
+  },
+  toFlag: function(aAttribute) {
+    for (let flag in statusNames)
+      if (statusNames[flag] == aAttribute)
+        return flag;
+    return imIStatusInfo.STATUS_UNKNOWN;
+  }
+};
new file mode 100644
--- /dev/null
+++ b/chat/modules/imTextboxUtils.jsm
@@ -0,0 +1,203 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2009.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const EXPORTED_SYMBOLS = [
+  "MessageFormat",
+  "TextboxSize",
+  "TextboxSpellChecker"
+];
+
+Components.utils.import("resource:///modules/imServices.jsm");
+const Ci = Components.interfaces;
+
+let MessageFormat = {
+  _observedPrefs: [],
+
+  getValues: function mf_getValues() {
+    this.unregisterObservers();
+    let langGroup =
+      Services.prefs.getComplexValue("font.language.group",
+                                     Ci.nsIPrefLocalizedString).data;
+    let fontGroup = Services.prefs.getCharPref("font.default." + langGroup);
+    let fontPref = "font.name." + fontGroup + "." + langGroup;
+    let fontSizePref = "font.size.variable." + langGroup;
+    this._values = {
+      langGroup: langGroup,
+      fontGroup: fontGroup,
+      font: Services.prefs.getCharPref(fontPref),
+      fontIsDefault: !Services.prefs.prefHasUserValue(fontPref),
+      fontSize: Services.prefs.getIntPref(fontSizePref),
+      fontSizeIsDefault: !Services.prefs.prefHasUserValue(fontSizePref),
+      defaultFontSize:
+        Services.prefs.getDefaultBranch(null).getIntPref(fontSizePref),
+      foregroundColor:
+        Services.prefs.getCharPref("browser.display.foreground_color"),
+      foregroundColorIsDefault:
+        !Services.prefs.prefHasUserValue("browser.display.foreground_color"),
+      useSystemColor:
+        Services.prefs.getBoolPref("browser.display.use_system_colors")
+    };
+
+    this._observedPrefs = [
+      "font.language.group",
+      "font.default." + langGroup,
+      "font.name." + fontGroup + "." + langGroup,
+      "font.size.variable." + langGroup,
+      "browser.display.foreground_color",
+      "browser.display.use_system_colors"
+    ];
+    for each (let name in this._observedPrefs)
+      Services.prefs.addObserver(name, this, false);
+  },
+  unregisterObservers: function mf_unregisterObservers() {
+    for each (let name in this._observedPrefs)
+      Services.prefs.removeObserver(name, this);
+    this._observedPrefs = [];
+  },
+  observe: function(aSubject, aTopic, aMsg) {
+    this.getValues();
+    for each (let textbox in this._textboxes)
+      this.styleTextbox(textbox);
+  },
+  _getColor: function mf__getColor() {
+    if (this._values.foregroundColorIsDefault || this._values.useSystemColor)
+      return "";
+    return this._values.foregroundColor;
+  },
+  styleTextbox: function mf_styleTextbox(aTextbox) {
+    aTextbox.style.color = this._getColor();
+    aTextbox.style.fontSize = this._values.fontSize + "px";
+    aTextbox.style.fontFamily = this._values.font;
+  },
+  getMessageStyle: function mf_getMessageStyle() {
+    let result = {};
+
+    let color = this._getColor();
+    if (color)
+      result.color = color;
+
+    if (!this._values.fontSizeIsDefault) {
+      result.fontSize = this._values.fontSize;
+      result.defaultFontSize = this._values.defaultFontSize;
+    }
+
+    if (!this._values.fontIsDefault)
+      result.fontFamily = this._values.font;
+
+    return result;
+  },
+  _textboxes: [],
+  registerTextbox: function mf_registerTextbox(aTextbox) {
+    if (this._textboxes.indexOf(aTextbox) == -1)
+      this._textboxes.push(aTextbox);
+
+    if (this._textboxes.length == 1)
+      this.getValues();
+
+    this.styleTextbox(aTextbox);
+  },
+  unregisterTextbox: function(aTextbox) {
+    let index = this._textboxes.indexOf(aTextbox);
+    if (index != -1)
+      this._textboxes.splice(index, 1);
+
+    if (!this._textboxes.length)
+      this.unregisterObservers();
+  }
+};
+
+let TextboxSize = {
+  _textboxAutoResizePrefName: "messenger.conversations.textbox.autoResize",
+  get autoResize() {
+    delete this.autoResize;
+    Services.prefs.addObserver(this._textboxAutoResizePrefName, this, false);
+    return this.autoResize =
+      Services.prefs.getBoolPref(this._textboxAutoResizePrefName);
+  },
+  observe: function(aSubject, aTopic, aMsg) {
+    if (aTopic == "nsPref:changed" && aMsg == this._textboxAutoResizePrefName)
+      this.autoResize = Services.prefs.getBoolPref(aMsg);
+  }
+};
+
+let TextboxSpellChecker = {
+#ifndef MOZ_THUNDERBIRD
+  _spellCheckPrefName: "layout.spellcheckDefault",
+#else
+  _spellCheckPrefName: "mail.spellcheck.inline",
+#endif
+  _enabled: false,
+ getValue: function tsc_getValue() {
+#ifndef MOZ_THUNDERBIRD
+    this._enabled = !!Services.prefs.getIntPref(this._spellCheckPrefName);
+#else
+    this._enabled = Services.prefs.getBoolPref(this._spellCheckPrefName);
+#endif
+  },
+  applyValue: function tsc_applyValue(aTextbox) {
+    if (this._enabled)
+      aTextbox.setAttribute("spellcheck", "true");
+    else
+      aTextbox.removeAttribute("spellcheck");
+  },
+
+  _textboxes: [],
+  registerTextbox: function tsc_registerTextbox(aTextbox) {
+    if (this._textboxes.indexOf(aTextbox) == -1)
+      this._textboxes.push(aTextbox);
+
+    if (this._textboxes.length == 1) {
+      Services.prefs.addObserver(this._spellCheckPrefName, this, false);
+      this.getValue();
+    }
+
+    this.applyValue(aTextbox);
+  },
+  unregisterTextbox: function tsc_unregisterTextbox(aTextbox) {
+    let index = this._textboxes.indexOf(aTextbox);
+    if (index != -1)
+      this._textboxes.splice(index, 1);
+
+    if (!this._textboxes.length)
+      Services.prefs.removeObserver(this._spellCheckPrefName, this);
+  },
+  observe: function tsc_observe(aSubject, aTopic, aMsg) {
+    this.getValue();
+    for each (let textbox in this._textboxes)
+      this.applyValue(textbox);
+  }
+};
new file mode 100644
--- /dev/null
+++ b/chat/modules/imThemes.jsm
@@ -0,0 +1,1041 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2009.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const EXPORTED_SYMBOLS = [
+  "getCurrentTheme",
+  "getThemeByName",
+  "getHTMLForMessage",
+  "getThemeVariants",
+  "isNextMessage",
+  "insertHTMLForMessage",
+  "initHTMLDocument",
+  "getMessagesForRange",
+  "serializeSelection"
+];
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource:///modules/imServices.jsm");
+Cu.import("resource://gre/modules/DownloadUtils.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const kMessagesStylePrefBranch = "messenger.options.messagesStyle.";
+const kThemePref = "theme";
+const kVariantPref = "variant";
+const kShowHeaderPref = "showHeader";
+const kCombineConsecutivePref = "combineConsecutive";
+const kCombineConsecutiveIntervalPref = "combineConsecutiveInterval";
+
+const DEFAULT_THEME = "bubbles";
+const DEFAULT_THEMES = ["bubbles", "dark", "papersheets", "simple"];
+
+const kLineBreak = "@mozilla.org/windows-registry-key;1" in Cc ? "\r\n" : "\n";
+
+XPCOMUtils.defineLazyGetter(this, "gPrefBranch", function()
+  Services.prefs.getBranch(kMessagesStylePrefBranch)
+);
+
+XPCOMUtils.defineLazyGetter(this, "TXTToHTML", function() {
+  let cs = Cc["@mozilla.org/txttohtmlconv;1"].getService(Ci.mozITXTToHTMLConv);
+  return function(aTXT) cs.scanTXT(aTXT, cs.kEntities);
+});
+
+var gCurrentTheme = null;
+
+function getChromeFile(aURI)
+{
+  try {
+    let channel = Services.io.newChannel(aURI, null, null);
+    let stream = channel.open();
+    let sstream = Components.classes["@mozilla.org/scriptableinputstream;1"]
+                            .createInstance(Ci.nsIScriptableInputStream);
+    sstream.init(stream);
+    let text = sstream.read(sstream.available());
+    sstream.close();
+    return text;
+  } catch (e) {
+    if (e.result != Components.results.NS_ERROR_FILE_NOT_FOUND)
+      dump("Getting " + aURI + ": " + e + "\n");
+    return null;
+  }
+}
+
+function HTMLTheme(aBaseURI)
+{
+  let files = {
+    footer: "Footer.html",
+    header: "Header.html",
+    status: "Status.html",
+    statusNext: "NextStatus.html",
+    incomingContent: "Incoming/Content.html",
+    incomingContext: "Incoming/Context.html",
+    incomingNextContent: "Incoming/NextContent.html",
+    incomingNextContext: "Incoming/NextContext.html",
+    outgoingContent: "Outgoing/Content.html",
+    outgoingContext: "Outgoing/Context.html",
+    outgoingNextContent: "Outgoing/NextContent.html",
+    outgoingNextContext: "Outgoing/NextContext.html"
+  };
+
+  for (let id in files) {
+    let html = getChromeFile(aBaseURI + files[id]);
+    if (html)
+      this[id] = html;
+  }
+
+  if (!("incomingContent" in files))
+    throw "Invalid theme: Incoming/Content.html is missing!";
+
+  // We set the prototype this way to workaround the
+  // 'setting a property that has only a getter' error.
+  this.__proto__ = HTMLTheme_prototype;
+}
+
+const HTMLTheme_prototype = {
+  get footer() "",
+  get header() "",
+  get status() this.incomingContent,
+  get statusNext() this.status,
+  get incomingContent() {
+    throw "Incoming/Content.html is a required file";
+  },
+  get incomingNextContent() this.incomingContent,
+  get outgoingContent() this.incomingContent,
+  get outgoingNextContent() this.incomingNextContent,
+  get incomingContext() this.incomingContent,
+  get incomingNextContext() this.incomingNextContent,
+  get outgoingContext() this.hasOwnProperty("outgoingContent") ? this.outgoingContent : this.incomingContext,
+  get outgoingNextContext() this.hasOwnProperty("outgoingNextContent") ? this.outgoingNextContent : this.incomingNextContext
+};
+
+function plistToJSON(aElt)
+{
+  switch (aElt.localName) {
+    case 'true':
+      return true;
+    case 'false':
+      return false;
+    case 'string':
+    case 'data':
+      return aElt.textContent;
+    case 'real':
+      return parseFloat(aElt.textContent);
+    case 'integer':
+      return parseInt(aElt.textContent, 10);
+
+    case 'dict':
+      let res = {};
+      let nodes = aElt.childNodes;
+      for (let i = 0; i < nodes.length; ++i) {
+        if (nodes[i].nodeName == 'key') {
+          let key = nodes[i].textContent;
+          ++i;
+          while (!(nodes[i] instanceof Ci.nsIDOMElement))
+            ++i;
+          res[key] = plistToJSON(nodes[i]);
+        }
+      }
+      return res;
+
+    case 'array':
+      let array = [];
+      nodes = aElt.childNodes;
+      for (let i = 0; i < nodes.length; ++i) {
+        if (nodes[i] instanceof Ci.nsIDOMElement)
+          array.push(plistToJSON(nodes[i]));
+      }
+      return array;
+
+    default:
+      throw "Unknown tag in plist file";
+  }
+}
+
+function getInfoPlistContent(aBaseURI)
+{
+  try {
+    let channel = Services.io.newChannel(aBaseURI + "Info.plist", null, null);
+    let stream = channel.open();
+    let parser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
+                           .createInstance(Ci.nsIDOMParser);
+    let doc = parser.parseFromStream(stream, null, stream.available(), "text/xml");
+    if (doc.documentElement.localName != "plist")
+      throw "Invalid Info.plist file";
+    let node = doc.documentElement.firstChild;
+    while (node && !(node instanceof Ci.nsIDOMElement))
+      node = node.nextSibling;
+    if (!node || node.localName != "dict")
+      throw "Empty or invalid Info.plist file";
+    return plistToJSON(node);
+  } catch(e) {
+    Components.utils.reportError(e);
+    return null;
+  }
+}
+
+function getChromeBaseURI(aThemeName)
+{
+  if (DEFAULT_THEMES.indexOf(aThemeName) != -1)
+    return "chrome://instantbird-messagestyles/skin/" + aThemeName + "/";
+  return "chrome://" + aThemeName + "/skin/";
+}
+
+function getThemeByName(aName)
+{
+  let baseURI = getChromeBaseURI(aName);
+  let metadata = getInfoPlistContent(baseURI);
+  if (!metadata)
+    throw "Cannot load theme " + aName;
+
+  return {
+    name: aName,
+    variant: "default",
+    baseURI: baseURI,
+    metadata: metadata,
+    html: new HTMLTheme(baseURI),
+    showHeader: gPrefBranch.getBoolPref(kShowHeaderPref),
+    combineConsecutive: gPrefBranch.getBoolPref(kCombineConsecutivePref),
+    combineConsecutiveInterval: gPrefBranch.getIntPref(kCombineConsecutiveIntervalPref)
+  };
+}
+
+function getCurrentTheme()
+{
+  let name = gPrefBranch.getCharPref(kThemePref);
+  let variant = gPrefBranch.getCharPref(kVariantPref);
+  if (gCurrentTheme && gCurrentTheme.name == name &&
+      gCurrentTheme.variant == variant)
+    return gCurrentTheme;
+
+  try {
+    gCurrentTheme = getThemeByName(name);
+    gCurrentTheme.variant = variant;
+  } catch(e) {
+    Components.utils.reportError(e);
+    gCurrentTheme = getThemeByName(DEFAULT_THEME);
+    gCurrentTheme.variant = "default";
+  }
+
+  return gCurrentTheme;
+}
+
+function getDirectoryEntries(aDir)
+{
+  let ios = Services.io;
+  let uri = ios.newURI(aDir, null, null);
+  let cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
+                     .getService(Ci.nsIXULChromeRegistry);
+  while (uri.scheme == "chrome")
+    uri = cr.convertChromeURL(uri);
+
+  // remove any trailing file name added by convertChromeURL
+  let spec = uri.spec.replace(/[^\/]+$/, "");
+  uri = ios.newURI(spec, null, null);
+
+  let results = [];
+  if (uri.scheme == "jar") {
+    uri.QueryInterface(Ci.nsIJARURI);
+    let strEntry = uri.JAREntry;
+    if (!strEntry)
+      return [];
+
+    let zr = Components.classes["@mozilla.org/libjar/zip-reader;1"]
+                       .createInstance(Ci.nsIZipReader);
+    let jarFile = uri.JARFile;
+    if (jarFile instanceof Ci.nsIJARURI) {
+      let innerZr = Components.classes["@mozilla.org/libjar/zip-reader;1"]
+                              .createInstance(Ci.nsIZipReader);
+      innerZr.open(jarFile.JARFile.QueryInterface(Ci.nsIFileURL).file);
+      zr.openInner(innerZr, jarFile.JAREntry);
+    }
+    else
+      zr.open(jarFile.QueryInterface(Ci.nsIFileURL).file);
+
+    if (!zr.hasEntry(strEntry) || !zr.getEntry(strEntry).isDirectory) {
+      zr.close();
+      return [];
+    }
+
+    let escapedEntry = strEntry.replace(/([*?$[\]^~()\\])/g, "\\$1");
+    let filter = escapedEntry + "?*~" + escapedEntry + "?*/?*";
+    let entries = zr.findEntries(filter);
+
+    let parentLength = strEntry.length;
+    while (entries.hasMore())
+      results.push(entries.getNext().substring(parentLength));
+    zr.close();
+  }
+  else if (uri.scheme == "file") {
+    uri.QueryInterface(Ci.nsIFileURL);
+    let dir = uri.file;
+
+    if (!dir.exists() || !dir.isDirectory())
+      return [];
+
+    let children = dir.directoryEntries;
+    while (children.hasMoreElements()) {
+      let file = children.getNext()
+                         .QueryInterface(Ci.nsIFile);
+      results.push(file.leafName);
+    }
+  }
+
+  return results;
+}
+
+function getThemeVariants(aTheme)
+{
+  let variants = getDirectoryEntries(aTheme.baseURI + "Variants/");
+  let cssRe = /\.css$/;
+  return variants.filter(function(v) cssRe.test(v))
+                 .map(function(v) v.replace(cssRe, ""));
+}
+
+/* helper function for replacements in messages */
+function getBuddyFromMessage(aMsg)
+{
+  if (aMsg.incoming) {
+    let conv = aMsg.conversation;
+    if (!conv.isChat)
+      return conv.buddy;
+  }
+
+  return null;
+}
+
+function getStatusIconFromBuddy(aBuddy)
+{
+  let status = "unknown";
+  if (aBuddy) {
+    if (!aBuddy.online)
+      status = "offline";
+    else if (aBuddy.idle)
+      status = "idle";
+    else if (!aBuddy.available)
+      status = "away";
+    else
+      status = "available";
+  }
+
+  return "chrome://chat/skin/" + status + "-16.png";
+}
+
+const headerFooterReplacements = {
+  chatName: function(aConv) TXTToHTML(aConv.title),
+  sourceName: function(aConv) TXTToHTML(aConv.account.alias || aConv.account.name),
+  destinationName: function(aConv) TXTToHTML(aConv.name),
+  destinationDisplayName: function(aConv) TXTToHTML(aConv.title),
+  incomingIconPath: function(aConv) {
+    let buddy;
+    return (!aConv.isChat && (buddy = aConv.buddy) &&
+            buddy.buddyIconFilename) || "incoming_icon.png";
+  },
+  outgoingIconPath: function(aConv) "outgoing_icon.png",
+  timeOpened: function(aConv, aFormat) {
+    if (aFormat)
+      return (new Date()).toLocaleFormat(aFormat);
+    else
+      return (new Date()).toLocaleTimeString();
+  }
+};
+
+function formatAutoResponce(aTxt)
+  Services.strings
+          .createBundle("chrome://chat/locale/conversations.properties")
+          .formatStringFromName("autoReply", [aTxt], 1)
+
+const statusMessageReplacements = {
+  message: function(aMsg) "<span class=\"ib-msg-txt\">" +
+                          (aMsg.autoResponse ? formatAutoResponce(aMsg.message) : aMsg.message) +
+                          "</span>",
+  time: function(aMsg, aFormat) {
+    let date = new Date(aMsg.time * 1000);
+    if (aFormat)
+      return date.toLocaleFormat(aFormat);
+    return date.toLocaleTimeString();
+  },
+  timestamp: function(aMsg) aMsg.time,
+  shortTime: function(aMsg) (new Date(aMsg.time * 1000)).toLocaleTimeString(),
+  messageClasses: function(aMsg) {
+    let msgClass = [];
+    if (/^(<[^>]+>)*\/me /.test(aMsg.originalMessage))
+      msgClass.push("action");
+
+    if (!aMsg.system) {
+      msgClass.push("message");
+      if (aMsg.incoming)
+        msgClass.push("incoming");
+      else
+        if (aMsg.outgoing)
+          msgClass.push("outgoing");
+
+      if (aMsg.autoResponse)
+        msgClass.push("autoreply");
+    }
+    else
+      msgClass.push("event");
+
+    if (aMsg.containsNick)
+      msgClass.push("nick");
+    if (aMsg.error)
+      msgClass.push("error");
+    if (aMsg.delayed)
+      msgClass.push("delayed");
+    if (aMsg.notification)
+      msgClass.push("notification");
+
+    return msgClass.join(" ");
+  }
+};
+
+const messageReplacements = {
+  userIconPath: function (aMsg) {
+    // If the protocol plugin provides an icon for the message, use it.
+    let iconURL = aMsg.iconURL;
+    if (iconURL)
+      return iconURL;
+
+    // For outgoing messages, use the current user icon.
+    if (aMsg.outgoing) {
+      iconURL = aMsg.conversation.account.statusInfo.getUserIcon();
+      if (iconURL)
+        return iconURL.spec;
+    }
+
+    // Fallback to the theme's default icons.
+    return (aMsg.incoming ? "Incoming" : "Outgoing") + "/buddy_icon.png";
+  },
+  senderScreenName: function(aMsg) TXTToHTML(aMsg.who),
+  sender: function(aMsg) TXTToHTML(aMsg.alias || aMsg.who),
+  senderColor: function(aMsg) aMsg.color,
+  senderStatusIcon: function(aMsg)
+    getStatusIconFromBuddy(getBuddyFromMessage(aMsg)),
+  messageDirection: function(aMsg) "ltr",
+  // no theme actually use this, don't bother making sure this is the real
+  // serverside alias
+  senderDisplayName: function(aMsg) TXTToHTML(aMsg.alias || aMsg.who),
+  service: function(aMsg) aMsg.conversation.account.protocol.name,
+  textbackgroundcolor: function(aMsg, aFormat) "transparent", // FIXME?
+  __proto__: statusMessageReplacements
+};
+
+const statusReplacements = {
+  status: function(aMsg) "", //FIXME
+  statusIcon: function(aMsg) {
+    let conv = aMsg.conversation;
+    let buddy = null;
+    if (!conv.isChat)
+      buddy = conv.buddy;
+    return getStatusIconFromBuddy(buddy);
+  },
+  __proto__: statusMessageReplacements
+};
+
+const kReplacementRegExp = /%([a-zA-Z]*)(\{([^\}]*)\})?%/g;
+
+function replaceKeywordsInHTML(aHTML, aReplacements, aReplacementArg)
+{
+  kReplacementRegExp.lastIndex = 0;
+  let previousIndex = 0;
+  let result = "";
+  let match;
+  while ((match = kReplacementRegExp.exec(aHTML))) {
+    let content = "";
+    if (match[1] in aReplacements)
+      content = aReplacements[match[1]](aReplacementArg, match[3]);
+    else
+      Components.utils.reportError("Unknown replacement string %" +
+                                   match[1] + "% in message styles.");
+    result += aHTML.substring(previousIndex, match.index) + content;
+    previousIndex = kReplacementRegExp.lastIndex;
+  }
+
+  return result + aHTML.slice(previousIndex);
+}
+
+function isNextMessage(aTheme, aMsg, aPreviousMsg)
+{
+  if (!aTheme.combineConsecutive ||
+      (hasMetadataKey(aTheme, "DisableCombineConsecutive") &&
+       getMetadata(aTheme, "DisableCombineConsecutive")))
+    return false;
+
+  if (!aPreviousMsg)
+    return false;
+
+  if (aMsg.system && aPreviousMsg.system)
+    return true;
+
+  if (aMsg.who != aPreviousMsg.who ||
+      aMsg.outgoing != aPreviousMsg.outgoing ||
+      aMsg.incoming != aPreviousMsg.incoming)
+    return false;
+
+  let timeDifference = aMsg.time - aPreviousMsg.time;
+  return (timeDifference >= 0 &&
+          timeDifference <= aTheme.combineConsecutiveInterval);
+}
+
+function getHTMLForMessage(aMsg, aTheme, aIsNext, aIsContext)
+{
+  let html, replacements;
+  if (aMsg.system) {
+    html = aIsNext ? aTheme.html.statusNext : aTheme.html.status;
+    replacements = statusReplacements;
+  }
+  else {
+    html = aMsg.incoming ? "incoming" : "outgoing";
+    if (aIsNext)
+      html += "Next";
+    html += aIsContext ? "Context" : "Content";
+    html = aTheme.html[html];
+    replacements = messageReplacements;
+    let meRegExp = /^((<[^>]+>)*)\/me /;
+    if (meRegExp.test(aMsg.message)) {
+      aMsg.message = aMsg.message.replace(meRegExp, "$1");
+      let actionMessageTemplate = "* %message% *";
+      if (hasMetadataKey(aTheme, "ActionMessageTemplate"))
+        actionMessageTemplate = getMetadata(aTheme, "ActionMessageTemplate");
+      html = html.replace(/%message%/g, actionMessageTemplate);
+    }
+  }
+
+  return replaceKeywordsInHTML(html, replacements, aMsg);
+}
+
+function insertHTMLForMessage(aMsg, aHTML, aDoc, aIsNext)
+{
+  let insert = aDoc.getElementById("insert");
+  if (insert && !aIsNext) {
+    insert.parentNode.removeChild(insert);
+    insert = null;
+  }
+
+  let range = aDoc.createRange();
+  let parent = insert ? insert.parentNode : aDoc.getElementById("Chat");
+  range.selectNode(parent);
+  let documentFragment = range.createContextualFragment(aHTML);
+  let result = documentFragment.firstChild;
+
+  // store the prplIMessage object in each of the "root" node that
+  // will be inserted into the document, so that selection code can
+  // retrieve the message by just looking at the parent node until it
+  // finds something.
+  for (let root = result; root; root = root.nextSibling)
+    root._originalMsg = aMsg;
+
+  // make sure the result is an HTMLElement and not some whitespace...
+  while (result && !(result instanceof Ci.nsIDOMHTMLElement))
+    result = result.nextSibling;
+  if (insert)
+    parent.replaceChild(documentFragment, insert);
+  else
+    parent.appendChild(documentFragment);
+  return result;
+}
+
+function hasMetadataKey(aTheme, aKey)
+{
+  return (aKey in aTheme.metadata) ||
+         ((aTheme.variant != "default") &&
+          (aKey + ":" + aTheme.variant) in aTheme.metadata) ||
+         (("DefaultVariant" in aTheme.metadata) &&
+          ((aKey + ":" + aTheme.metadata.DefaultVariant) in aTheme.metadata));
+}
+
+function getMetadata(aTheme, aKey)
+{
+  if ((aTheme.variant != "default") &&
+      (aKey + ":" + aTheme.variant) in aTheme.metadata)
+    return aTheme.metadata[aKey + ":" + aTheme.variant];
+
+  if (("DefaultVariant" in aTheme.metadata) &&
+      ((aKey + ":" + aTheme.metadata.DefaultVariant) in aTheme.metadata))
+    return aTheme.metadata[aKey + ":" + aTheme.metadata.DefaultVariant];
+
+  return aTheme.metadata[aKey];
+}
+
+function initHTMLDocument(aConv, aTheme, aDoc)
+{
+  let HTML = "<html><head><base href=\"" + aTheme.baseURI + "\"/>";
+
+  // Screen readers may read the title of the document, so provide one
+  // to avoid an ugly fallback to the URL (see bug 1165).
+  HTML += "<title>" + aConv.title + "</title>";
+
+  function addCSS(aHref)
+  {
+    HTML += "<link rel=\"stylesheet\" href=\"" + aHref + "\" type=\"text/css\"/>";
+  }
+  addCSS("chrome://chat/skin/conv.css");
+
+  // add css to handle DefaultFontFamily and DefaultFontSize
+  let cssText = "";
+  if (hasMetadataKey(aTheme, "DefaultFontFamily"))
+    cssText += "font-family: " + getMetadata(aTheme, "DefaultFontFamily") + ";";
+  if (hasMetadataKey(aTheme, "DefaultFontSize"))
+    cssText += "font-size: " + getMetadata(aTheme, "DefaultFontSize") + ";";
+  if (cssText)
+    addCSS("data:text/css,*{ " + cssText + " }");
+
+  // add the main CSS file of the theme
+  if (aTheme.metadata.MessageViewVersion >= 3 || aTheme.variant == "default")
+    addCSS("main.css");
+
+  // add the CSS file of the variant
+  if (aTheme.variant != "default")
+    addCSS("Variants/" + aTheme.variant + ".css");
+  else
+    if ("DefaultVariant" in aTheme.metadata)
+      addCSS("Variants/" + aTheme.metadata.DefaultVariant + ".css");
+
+  HTML += "</head><body id=\"ibcontent\">";
+
+  // We insert the whole content of body: header, chat div, footer
+  if (aTheme.showHeader) {
+    HTML += replaceKeywordsInHTML(aTheme.html.header,
+                                  headerFooterReplacements, aConv);
+  }
+  HTML += "<div id=\"Chat\"></div>";
+  HTML += replaceKeywordsInHTML(aTheme.html.footer,
+                                headerFooterReplacements, aConv);
+  aDoc.open();
+  aDoc.write(HTML + "</body></html>");
+  aDoc.close();
+  aDoc.defaultView.convertTimeUnits = DownloadUtils.convertTimeUnits;
+}
+
+/* Selection stuff */
+function getEllipsis()
+{
+  let ellipsis = "[\u2026]";
+
+  try {
+    ellipsis =
+      Services.prefs
+              .getComplexValue("messenger.conversations.selections.ellipsis",
+                               Ci.nsIPrefLocalizedString).data;
+  } catch (e) { }
+  return ellipsis;
+}
+
+function _serializeDOMObject(aDocument, aInitFunction)
+{
+  // This shouldn't really be a constant, as we want to support
+  // text/html too in the future.
+  const type = "text/plain"; 
+
+  let encoder =
+    Components.classes["@mozilla.org/layout/documentEncoder;1?type=" + type]
+              .createInstance(Ci.nsIDocumentEncoder);
+  encoder.init(aDocument, type, 0);
+  aInitFunction(encoder);
+  let result = encoder.encodeToString();
+  return result;
+}
+
+function serializeRange(aRange)
+{
+  return _serializeDOMObject(aRange.startContainer.ownerDocument,
+                             function(aEncoder) { aEncoder.setRange(aRange); });
+}
+
+function serializeNode(aNode)
+{
+  return _serializeDOMObject(aNode.ownerDocument,
+                             function(aEncoder) { aEncoder.setNode(aNode); });
+}
+
+/* This function is used to pretty print a selection inside a conversation area */
+function serializeSelection(aSelection)
+{
+  // We have two kinds of selection serialization:
+  //  - The short version, used when only a part of message is
+  //    selected, or if nothing interesting is selected
+  let shortSelection = "";
+
+  //  - The long version, which is used:
+  //      * when both some of the message text and some of the context
+  //        (sender, time, ...) is selected;
+  //      * when several messages are selected at once
+  //    This version uses an array, with each message formatted
+  //    through the theme system.
+  let longSelection = [];
+
+  // We first assume that we are going to use the short version, but
+  // while working on creating the short version, we prepare
+  // everything to be able to switch to the long version if we later
+  // discover that it is in fact needed.
+  let shortVersionPossible = true;
+
+  // Sometimes we need to know if a selection range is inside the same
+  // message as the previous selection range, so we keep track of the
+  // last message we have processed.
+  let lastMessage = null;
+
+  for (let i = 0; i < aSelection.rangeCount; ++i) {
+    let range = aSelection.getRangeAt(i);
+    let messages = getMessagesForRange(range);
+
+    // If at least one selected message has some of its text selected,
+    // remove from the selection all the messages that have no text
+    // selected
+    let testFunction = function(msg) msg.isTextSelected();
+    if (messages.some(testFunction))
+      messages = messages.filter(testFunction);
+
+    if (!messages.length) {
+      // Do it only if it wouldn't override a better already found selection
+      if (!shortSelection)
+        shortSelection = serializeRange(range);
+      continue;
+    }
+
+    if (shortVersionPossible && messages.length == 1 &&
+        (!messages[0].isTextSelected() || messages[0].onlyTextSelected()) &&
+        (!lastMessage || lastMessage.msg == messages[0].msg ||
+         lastMessage.msg.who == messages[0].msg.who)) {
+      if (shortSelection) {
+        if (lastMessage.msg != messages[0].msg) {
+          // Add the ellipsis only if the previous message was cut
+          if (lastMessage.cutEnd)
+            shortSelection += " " + getEllipsis();
+          shortSelection += kLineBreak;
+        }
+        else
+          shortSelection += " " + getEllipsis() + " ";
+      }
+      shortSelection += serializeRange(range);
+      longSelection.push(messages[0].getFormattedMessage());
+    }
+    else {
+      shortVersionPossible = false;
+      for (let m = 0; m < messages.length; ++m) {
+        let message = messages[m];
+        if (m == 0 && lastMessage && lastMessage.msg == message.msg) {
+          let text = message.getSelectedText();
+          if (message.cutEnd)
+            text += " " + getEllipsis();
+          longSelection[longSelection.length - 1] += " " + text;
+        }
+        else
+          longSelection.push(message.getFormattedMessage());
+      }
+    }
+    lastMessage = messages[messages.length - 1];
+  }
+
+  if (shortVersionPossible)
+    return shortSelection || aSelection.toString();
+  else
+    return longSelection.join(kLineBreak);
+}
+
+function SelectedMessage(aRootNode, aRange)
+{
+  this._rootNodes = [aRootNode];
+  this._range = aRange;
+}
+
+SelectedMessage.prototype = {
+  get msg() this._rootNodes[0]._originalMsg,
+  addRoot: function(aRootNode) {
+    this._rootNodes.push(aRootNode);
+  },
+
+  // Helper function that returns the first span node of class
+  // ib-msg-text under the rootNodes of the selected message.
+  _getSpanNode: function() {
+    // first use the cached value if any
+    if (this._spanNode)
+      return this._spanNode;
+
+    let spanNode = null;
+    const NodeFilter = Ci.nsIDOMNodeFilter;
+    // helper filter function for the tree walker
+    let filter = function(node) {
+      return node.className == "ib-msg-txt" ? NodeFilter.FILTER_ACCEPT
+                                            : NodeFilter.FILTER_SKIP;
+    };
+    // walk the DOM subtrees of each root, keep the first correct span node
+    for (let i = 0; !spanNode && i < this._rootNodes.length; ++i) {
+      let rootNode = this._rootNodes[i];
+      // the TreeWalker doesn't test the root node, special case it first
+      if (filter(rootNode) == NodeFilter.FILTER_ACCEPT) {
+        spanNode = rootNode;
+        break;
+      }
+      let treeWalker =
+        rootNode.ownerDocument.createTreeWalker(rootNode,
+                                                NodeFilter.SHOW_ELEMENT,
+                                                {acceptNode: filter}, false);
+      spanNode = treeWalker.nextNode();
+    }
+
+    return (this._spanNode = spanNode);
+  },
+
+  // Initialize _textSelected and _otherSelected; if _textSelected is true,
+  // also initialize _selectedText and _cutBegin/End.
+  _initSelectedText: function() {
+    if ("_textSelected" in this)
+      return; // already initialized
+
+    let spanNode = this._getSpanNode();
+    if (!spanNode) {
+      // can happen if the message text is under a separate root node
+      // that isn't selected at all
+      this._textSelected = false;
+      this._otherSelected = true;
+      return;
+    }
+    let startPoint = this._range.comparePoint(spanNode, 0);
+    let endPoint = this._range.comparePoint(spanNode,
+                                            spanNode.childNodes.length);
+    if (startPoint <= 0 && endPoint >= 0) {
+      let range = this._range.cloneRange();
+      if (startPoint >= 0)
+        range.setStart(spanNode, 0);
+      if (endPoint <= 0)
+        range.setEnd(spanNode, spanNode.childNodes.length);
+      this._selectedText = serializeRange(range);
+
+      // if the selected text is empty, set _selectedText to false
+      // this happens if the carret is at the offset 0 in the span node
+      this._textSelected = this._selectedText != "";
+    }
+    else
+      this._textSelected = false;
+    if (this._textSelected) {
+      // to check if the start or end is cut, the result of
+      // comparePoint is not enough because the selection range may
+      // start or end in a text node instead of the span node
+
+      if (startPoint == -1) {
+        let range = spanNode.ownerDocument.createRange();
+        range.setStart(spanNode, 0);
+        range.setEnd(this._range.startContainer, this._range.startOffset);
+        this._cutBegin = serializeRange(range) != "";
+      }
+      else
+        this._cutBegin = false;
+
+      if (endPoint == 1) {
+        let range = spanNode.ownerDocument.createRange();
+        range.setStart(this._range.endContainer, this._range.endOffset);
+        range.setEnd(spanNode, spanNode.childNodes.length);
+        this._cutEnd = !/^(\r?\n)?$/.test(serializeRange(range));
+      }
+      else
+        this._cutEnd = false;
+    }
+    this._otherSelected =
+      (startPoint >= 0 || endPoint <= 0) && // eliminate most negative cases
+      (!this._textSelected ||
+       serializeRange(this._range).length > this._selectedText.length);
+  },
+  get cutBegin() {
+    this._initSelectedText();
+    return this._textSelected && this._cutBegin;
+  },
+  get cutEnd() {
+    this._initSelectedText();
+    return this._textSelected && this._cutEnd;
+  },
+  isTextSelected: function() {
+    this._initSelectedText();
+    return this._textSelected;
+  },
+  onlyTextSelected: function() {
+    this._initSelectedText();
+    return !this._otherSelected;
+  },
+  getSelectedText: function() {
+    this._initSelectedText();
+    return this._textSelected ? this._selectedText : "";
+  },
+  getFormattedMessage: function() {
+    // First, get the selected text
+    this._initSelectedText();
+    let msg = this.msg;
+    let text;
+    if (this._textSelected) {
+      // Add ellipsis is needed
+      text = (this._cutBegin ? getEllipsis() + " " : "") +
+             this._selectedText +
+             (this._cutEnd ? " " + getEllipsis() : "");
+    }
+    else {
+      let div = this._rootNodes[0].ownerDocument.createElement("div");
+      div.innerHTML = msg.autoResponse ? formatAutoResponce(msg.message) : msg.message;
+      text = serializeNode(div);
+    }
+
+    // then get the suitable replacements and templates for this message
+    let getLocalizedPrefWithDefault = function (aName, aDefault) {
+      try {
+        let prefBranch =
+          Services.prefs.getBranch("messenger.conversations.selections.");
+        return prefBranch.getComplexValue(aName,
+                                          Ci.nsIPrefLocalizedString).data;
+      } catch(e) {
+        return aDefault;
+      }
+    };
+    let html, replacements;
+    if (msg.system) {
+      replacements = statusReplacements;
+      html = getLocalizedPrefWithDefault("systemMessagesTemplate",
+                                         "%time% - %message%");
+    }
+    else {
+      replacements = messageReplacements;
+      if (/^(<[^>]+>)*\/me /.test(msg.originalMessage)) {
+        html = getLocalizedPrefWithDefault("actionMessagesTemplate",
+                                           "%time% * %sender% %message%");
+      }
+      else {
+        html = getLocalizedPrefWithDefault("contentMessagesTemplate",
+                                           "%time% - %sender%: %message%");
+      }
+    }
+
+    // override the default %message% replacement so that it doesn't
+    // add a span node.
+    // Also, this uses directly the text variable so that we don't
+    // have to change the content of msg.message and revert it
+    // afterwards.
+    replacements = {
+      message: function(aMsg) text,
+      __proto__: replacements
+    };
+
+    // Finally, let the theme system do the magic!
+    return replaceKeywordsInHTML(html, replacements, msg);
+  }
+};
+
+function getMessagesForRange(aRange)
+{
+  let result = []; // will hold the final result
+  let messages = {}; // used to prevent duplicate messages in the result array
+
+  // cache the range boundaries, they will be used a lot
+  let endNode = aRange.endContainer;
+  let startNode = aRange.startContainer;
+
+  // Helper function to recursively look for _originalMsg JS
+  // properties on DOM nodes, and stop when endNode is reached.
+  // Found nodes are pushed into the rootNodes array.
+  let processSubtree = function(aNode) {
+
+    if (aNode._originalMsg) {
+      // store the result
+      if (!(aNode._originalMsg.id in messages)) {
+        // we've found a new message!
+        let newMessage = new SelectedMessage(aNode, aRange);
+        messages[aNode._originalMsg.id] = newMessage;
+        result.push(newMessage);
+      }
+      else {
+        // we've found another root of an already known message
+        messages[aNode._originalMsg.id].addRoot(aNode);
+      }
+    }
+
+    // check if we have reached the end node
+    if (aNode == endNode)
+      return true;
+
+    // recurse through children
+    if (aNode instanceof Ci.nsIDOMHTMLElement) {
+      for (let i = 0; i < aNode.childNodes.length; ++i)
+        if (processSubtree(aNode.childNodes[i]))
+          return true;
+    }
+
+    return false;
+  };
+
+  let currentNode = aRange.commonAncestorContainer;
+  if (currentNode instanceof Ci.nsIDOMHTMLElement) {
+    // Determine the index of the first and last children of currentNode
+    // that we should process.
+    let found = false;
+    let start = 0;
+    if (currentNode == startNode) {
+      // we want to process all children
+      found = true;
+      start = aRange.startOffset;
+    }
+    else {
+      // startNode needs to be a direct child of currentNode
+      while (startNode.parentNode != currentNode)
+        startNode = startNode.parentNode;
+    }
+    let end;
+    if (currentNode == endNode)
+      end = aRange.endOffset;
+    else
+      end = currentNode.childNodes.length;
+
+    for (let i = start; i < end; ++i) {
+      let node = currentNode.childNodes[i];
+
+      // don't do anything until we find the startNode
+      found = found || node == startNode;
+      if (!found)
+        continue;
+
+      if (processSubtree(node))
+        break;
+    }
+  }
+
+  // The selection may not include any root node of the first touched
+  // message, in this case, the DOM traversal of the DOM range
+  // couldn't give us the first message. Make sure we actually have
+  // the message in which the range starts.
+  let firstRoot = aRange.startContainer;
+  while (firstRoot && !firstRoot._originalMsg)
+    firstRoot = firstRoot.parentNode;
+  if (firstRoot && !(firstRoot._originalMsg.id in messages))
+    result.unshift(new SelectedMessage(firstRoot, aRange));
+
+  return result;
+}
new file mode 100644
--- /dev/null
+++ b/chat/modules/imXPCOMUtils.jsm
@@ -0,0 +1,216 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2010.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Cloke <clokep@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const EXPORTED_SYMBOLS = [
+  "XPCOMUtils",
+  "setTimeout",
+  "clearTimeout",
+  "executeSoon",
+  "hasOwnProperty",
+  "nsSimpleEnumerator",
+  "EmptyEnumerator",
+  "ClassInfo",
+  "l10nHelper",
+  "initLogModule"
+];
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource:///modules/imServices.jsm");
+
+const DEBUG_MISC = 1; // Very verbose (= 'DEBUG')
+const DEBUG_INFO = 2; // Verbose (= 'LOG')
+const DEBUG_WARNING = 3;
+const DEBUG_ERROR = 4;
+
+function scriptError(aModule, aLevel, aMessage) {
+  // Only continue if we want to see this level of logging.
+  let logLevel = Services.prefs.getIntPref("purple.debug.loglevel");
+  if (logLevel > aLevel)
+    return;
+
+  dump(aModule + ": " + aMessage + "\n");
+
+  // Log a debug statement.
+  if (aLevel == DEBUG_INFO && logLevel == DEBUG_INFO) {
+    Services.console.logStringMessage(aMessage);
+    return;
+  }
+
+  let flag = Ci.nsIScriptError.warningFlag;
+  if (aLevel >= DEBUG_ERROR)
+    flag = Ci.nsIScriptError.errorFlag;
+
+  let scriptError =
+    Cc["@mozilla.org/scripterror;1"].createInstance(Ci.nsIScriptError);
+  let caller = Components.stack.caller;
+  let sourceLine = aModule || caller.sourceLine;
+  if (caller.name) {
+    if (sourceLine)
+      sourceLine += ": ";
+    sourceLine += caller.name;
+  }
+  scriptError.init(aMessage, caller.filename, sourceLine, caller.lineNumber,
+                   null, flag, "component javascript");
+  Services.console.logMessage(scriptError);
+}
+function initLogModule(aModule, aThis)
+{
+  aThis = Components.utils.getGlobalForObject(aThis);
+  aThis.DEBUG = scriptError.bind(aThis, aModule, DEBUG_MISC);
+  aThis.LOG   = scriptError.bind(aThis, aModule, DEBUG_INFO);
+  aThis.WARN  = scriptError.bind(aThis, aModule, DEBUG_WARNING);
+  aThis.ERROR = scriptError.bind(aThis, aModule, DEBUG_ERROR);
+}
+
+function setTimeout(aFunction, aDelay)
+{
+  let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+  let args = Array.prototype.slice.call(arguments, 2);
+  // A reference to the timer should be kept to ensure it won't be
+  // GC'ed before firing the callback.
+  let callback = {
+    _timer: timer,
+    notify: function (aTimer) { aFunction.apply(null, args); delete this._timer; }
+  };
+  timer.initWithCallback(callback, aDelay, Ci.nsITimer.TYPE_ONE_SHOT);
+  return timer;
+}
+function clearTimeout(aTimer)
+{
+  if (aTimer)
+    aTimer.cancel();
+}
+
+function executeSoon(aFunction)
+{
+  Services.tm.mainThread.dispatch(aFunction, Ci.nsIEventTarget.DISPATCH_NORMAL);
+}
+
+// Similar to Object.hasOwnProperty, but doesn't fail if the object
+// has a hasOwnProperty property set.
+function hasOwnProperty(aObject, aPropertyName)
+  Object.prototype.hasOwnProperty.call(aObject, aPropertyName)
+
+/* Common nsIClassInfo and QueryInterface implementation
+ * shared by all generic objects implemented in this file. */
+function ClassInfo(aInterfaces, aDescription)
+{
+  if (!(this instanceof ClassInfo))
+    return new ClassInfo(aInterfaces, aDescription);
+
+  if (!Array.isArray(aInterfaces))
+    aInterfaces = [aInterfaces];
+
+  for each (let i in aInterfaces)
+    if (typeof i == "string" && !(i in Ci))
+      Services.console.logStringMessage("ClassInfo: unknown interface " + i);
+
+  this._interfaces =
+    aInterfaces.map(function (i) typeof i == "string" ? Ci[i] : i);
+
+  this.classDescription = aDescription || "JS Proto Object";
+}
+ClassInfo.prototype = {
+  QueryInterface: function ClassInfo_QueryInterface(iid) {
+    if (iid.equals(Ci.nsISupports) || iid.equals(Ci.nsIClassInfo) ||
+        this._interfaces.some(function(i) i.equals(iid)))
+      return this;
+
+    throw Cr.NS_ERROR_NO_INTERFACE;
+  },
+  getInterfaces: function(countRef) {
+    let interfaces =
+      [Ci.nsIClassInfo, Ci.nsISupports].concat(this._interfaces);
+    countRef.value = interfaces.length;
+    return interfaces;
+  },
+  getHelperForLanguage: function(language) null,
+  contractID: null,
+  classID: null,
+  implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
+  flags: 0
+};
+
+function l10nHelper(aChromeURL)
+{
+  let bundle = Services.strings.createBundle(aChromeURL);
+  return function (aStringId) {
+    try {
+      if (arguments.length == 1)
+        return bundle.GetStringFromName(aStringId);
+      return bundle.formatStringFromName(aStringId,
+                                         Array.prototype.slice.call(arguments, 1),
+                                         arguments.length - 1);
+    } catch (e) {
+      Cu.reportError(e);
+      dump("Failed to get " + aStringId + "\n");
+      return aStringId;
+    }
+  };
+}
+
+/**
+ * Constructs an nsISimpleEnumerator for the given array of items.
+ * Copied from netwerk/test/httpserver/httpd.js
+ *
+ * @param items : Array
+ *   the items, which must all implement nsISupports
+ */
+function nsSimpleEnumerator(items)
+{
+  this._items = items;
+  this._nextIndex = 0;
+}
+nsSimpleEnumerator.prototype = {
+  hasMoreElements: function() this._nextIndex < this._items.length,
+  getNext: function() {
+    if (!this.hasMoreElements())
+      throw Cr.NS_ERROR_NOT_AVAILABLE;
+
+    return this._items[this._nextIndex++];
+  },
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsISimpleEnumerator])
+};
+
+const EmptyEnumerator = {
+  hasMoreElements: function() false,
+  getNext: function() { throw Cr.NS_ERROR_NOT_AVAILABLE; },
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsISimpleEnumerator])
+};
new file mode 100644
--- /dev/null
+++ b/chat/modules/jsProtoHelper.jsm
@@ -0,0 +1,787 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2010.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Cloke <clokep@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const EXPORTED_SYMBOLS = [
+  "GenericAccountPrototype",
+  "GenericAccountBuddyPrototype",
+  "GenericConvIMPrototype",
+  "GenericConvChatPrototype",
+  "GenericConvChatBuddyPrototype",
+  "GenericMessagePrototype",
+  "GenericProtocolPrototype",
+  "ForwardProtocolPrototype",
+  "Message",
+  "TooltipInfo"
+];
+
+const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
+
+Cu.import("resource:///modules/imXPCOMUtils.jsm");
+Cu.import("resource:///modules/imServices.jsm");
+
+initLogModule("jsProtoHelper", this);
+
+function normalize(aString) aString.replace(/[^a-z0-9]/gi, "").toLowerCase()
+
+const ForwardAccountPrototype = {
+  __proto__: ClassInfo("prplIAccount", "generic account object"),
+  _init: function _init(aBase) {
+    this._base = aBase;
+  },
+
+  observe: function(aSubject, aTopic, aData) {
+    this._base.observe(aSubject, aTopic, aData);
+  },
+  unInit: function() this._base.unInit(),
+  connect: function() this._base.connect(),
+  disconnect: function() this._base.disconnect(),
+  createConversation: function(aName) this._base.createConversation(aName),
+  addBuddy: function(aTag, aName) this._base.addBuddy(aTag, aName),
+  loadBuddy: function(aBuddy, aTag) this._base.loadBuddy(aBuddy, aTag),
+  requestBuddyInfo: function(aBuddyName) this._base.requestBuddyInfo(aBuddyName),
+  getChatRoomFields: function() this._base.getChatRoomFields(),
+  getChatRoomDefaultFieldValues: function(aDefaultChatName)
+    this._base.getChatRoomDefaultFieldValues(aDefaultChatName),
+  joinChat: function(aComponents) this._base.joinChat(aComponents),
+  setBool: function(aName, aVal) this._base.setBool(aName, aVal),
+  setInt: function(aName, aVal) this._base.setInt(aName, aVal),
+  setString: function(aName, aVal) this._base.setString(aName, aVal),
+
+  get canJoinChat() this._base.canJoinChat,
+  get normalizedName() this._base.normalizedName,
+  get proxyInfo() this._base.proxyInfo,
+  get connectionErrorReason() this._base.connectionErrorReason,
+  get HTMLEnabled() this._base.HTMLEnabled,
+  get noBackgroundColors() this._base.noBackgroundColors,
+  get autoResponses() this._base.autoResponses,
+  get singleFormatting() this._base.singleFormatting,
+  get noNewlines() this._base.noNewlines,
+  get noFontSizes() this._base.noFontSizes,
+  get noUrlDesc() this._base.noUrlDesc,
+  get noImages() this._base.noImages,
+  get maxMessageLength() this._base.maxMessageLength,
+
+  set proxyInfo(val) { this._base.proxyInfo = val; }
+};
+
+const GenericAccountPrototype = {
+  __proto__: ClassInfo("prplIAccount", "generic account object"),
+  get wrappedJSObject() this,
+  _init: function _init(aProtocol, aImAccount) {
+    this.protocol = aProtocol;
+    this.imAccount = aImAccount;
+  },
+  observe: function(aSubject, aTopic, aData) {},
+  unInit: function() {},
+  connect: function() { throw Cr.NS_ERROR_NOT_IMPLEMENTED; },
+  disconnect: function() { throw Cr.NS_ERROR_NOT_IMPLEMENTED; },
+  createConversation: function(aName) { throw Cr.NS_ERROR_NOT_IMPLEMENTED; },
+  joinChat: function(aComponents) { throw Cr.NS_ERROR_NOT_IMPLEMENTED; },
+  setBool: function(aName, aVal) {},
+  setInt: function(aName, aVal) {},
+  setString: function(aName, aVal) {},
+
+  get name() this.imAccount.name,
+  get connected() this.imAccount.connected,
+  get connecting() this.imAccount.connecting,
+  get disconnected() this.imAccount.disconnected,
+  get disconnecting() this.imAccount.disconnecting,
+  _connectionErrorReason: Ci.prplIAccount.NO_ERROR,
+  get connectionErrorReason() this._connectionErrorReason,
+
+  reportConnected: function() {
+    this.imAccount.observe(this, "account-connected", null);
+  },
+  reportConnecting: function(aConnectionStateMsg) {
+    if (!this.connecting)
+      this.imAccount.observe(this, "account-connecting", null);
+    if (aConnectionStateMsg)
+      this.imAccount.observe(this, "account-connect-progress", aConnectionStateMsg);
+  },
+  reportDisconnected: function() {
+    this.imAccount.observe(this, "account-disconnected", null);
+  },
+  reportDisconnecting: function(aConnectionErrorReason, aConnectionErrorMessage) {
+    this._connectionErrorReason = aConnectionErrorReason;
+    this.imAccount.observe(this, "account-disconnecting", aConnectionErrorMessage);
+  },
+
+  // Called when the user adds a new buddy from the UI.
+  addBuddy: function(aTag, aName) {
+    Services.contacts
+            .accountBuddyAdded(new AccountBuddy(this, null, aTag, aName));
+  },
+  // Called during startup for each of the buddies in the local buddy list.
+  loadBuddy: function(aBuddy, aTag) {
+   try {
+     return new AccountBuddy(this, aBuddy, aTag);
+   } catch (x) {
+     dump(x + "\n");
+     return null;
+   }
+  },
+  requestBuddyInfo: function(aBuddyName) {},
+  get canJoinChat() false,
+  getChatRoomFields: function() {
+    if (!this.chatRoomFields)
+      return EmptyEnumerator;
+
+    let fields = [];
+    for (let fieldName in this.chatRoomFields)
+      fields.push(new ChatRoomField(fieldName, this.chatRoomFields[fieldName]));
+    return new nsSimpleEnumerator(fields);
+  },
+  getChatRoomDefaultFieldValues: function(aDefaultChatName) {
+    if (!this.chatRoomFields)
+      return EmptyEnumerator;
+
+    let defaultFieldValues = [];
+    for (let fieldName in this.chatRoomFields)
+      defaultFieldValues[fieldName] = this.chatRoomFields[fieldName].default;
+
+    if (aDefaultChatName && "parseDefaultChatName" in this) {
+      let parsedDefaultChatName = this.parseDefaultChatName(aDefaultChatName);
+      for (let field in parsedDefaultChatName)
+        defaultFieldValues[field] = parsedDefaultChatName[field];
+    }
+
+    return new ChatRoomFieldValues(defaultFieldValues);
+  },
+
+  getPref: function (aName, aType)
+    this.prefs.prefHasUserValue(aName) ?
+      this.prefs["get" + aType + "Pref"](aName) :
+      this.protocol._getOptionDefault(aName),
+  getInt: function(aName) this.getPref(aName, "Int"),
+  getBool: function(aName) this.getPref(aName, "Bool"),
+  getString: function(aName) {
+    return this.prefs.prefHasUserValue(aName) ?
+             this.prefs.getComplexValue(aName, Ci.nsISupportsString).data :
+             this.protocol._getOptionDefault(aName);
+  },
+
+  get prefs() this._prefs ||
+    (this._prefs = Services.prefs.getBranch("messenger.account." +
+                                            this.imAccount.id + ".options.")),
+
+  get normalizedName() normalize(this.name),
+  get proxyInfo() { throw Cr.NS_ERROR_NOT_IMPLEMENTED; },
+  set proxyInfo(val) { throw Cr.NS_ERROR_NOT_IMPLEMENTED; },
+
+  get HTMLEnabled() false,
+  get HTMLEscapePlainText() false,
+  get noBackgroundColors() true,
+  get autoResponses() false,
+  get singleFormatting() false,
+  get noNewlines() false,
+  get noFontSizes() false,
+  get noUrlDesc() false,
+  get noImages() true,
+  get maxMessageLength() 0
+};
+
+
+const GenericAccountBuddyPrototype = {
+  __proto__: ClassInfo("imIAccountBuddy", "generic account buddy object"),
+  _init: function(aAccount, aBuddy, aTag, aUserName) {
+    if (!aBuddy && !aUserName)
+      throw "aUserName is required when aBuddy is null";
+
+    this._tag = aTag;
+    this._account = aAccount;
+    this._buddy = aBuddy;
+    if (aBuddy) {
+      let displayName = aBuddy.displayName;
+      if (displayName != aUserName)
+        this._serverAlias = displayName;
+    }
+    this._userName = aUserName;
+  },
+  unInit: function() {
+    delete this._tag;
+    delete this._account;
+    delete this._buddy;
+  },
+
+  get account() this._account.imAccount,
+  set buddy(aBuddy) {
+    if (this._buddy)
+      throw Cr.NS_ERROR_ALREADY_INITIALIZED;
+    this._buddy = aBuddy;
+  },
+  get buddy() this._buddy,
+  get tag() this._tag,
+  set tag(aNewTag) {
+    let oldTag = this._tag;
+    this._tag = aNewTag;
+    Services.contacts.accountBuddyMoved(this, oldTag, aNewTag);
+  },
+
+  _notifyObservers: function(aTopic, aData) {
+    this._buddy.observe(this, "account-buddy-" + aTopic, aData);
+  },
+
+  _userName: "",
+  get userName() this._userName || this._buddy.userName,
+  get normalizedName()
+    this._userName ? normalize(this._userName) : this._buddy.normalizedName,
+  _serverAlias: "",
+  get serverAlias() this._serverAlias,
+  set serverAlias(aNewAlias) {
+    let old = this.displayName;
+    this._serverAlias = aNewAlias;
+    if (old != this.displayName)
+      this._notifyObservers("display-name-changed", old);
+  },
+
+  remove: function() {
+    Services.contacts.accountBuddyRemoved(this);
+  },
+
+  // imIStatusInfo implementation
+  get displayName() this.serverAlias || this.userName,
+  _buddyIconFileName: "",
+  get buddyIconFilename() this._buddyIconFileName,
+  set buddyIconFilename(aNewFileName) {
+    this._buddyIconFileName = aNewFileName;
+    this._notifyObservers("icon-changed");
+  },
+  _statusType: 0,
+  get statusType() this._statusType,
+  get online() this._statusType > Ci.imIStatusInfo.STATUS_OFFLINE,
+  get available() this._statusType == Ci.imIStatusInfo.STATUS_AVAILABLE,
+  get idle() this._statusType == Ci.imIStatusInfo.STATUS_IDLE,
+  get mobile() this._statusType == Ci.imIStatusInfo.STATUS_MOBILE,
+  _statusText: "",
+  get statusText() this._statusText,
+
+  // This is for use by the protocol plugin, it's not exposed in the
+  // imIStatusInfo interface.
+  // All parameters are optional and will be ignored if they are null
+  // or undefined.
+  setStatus: function(aStatusType, aStatusText, aAvailabilityDetails) {
+    // Ignore omitted parameters.
+    if (aStatusType === undefined || aStatusType === null)
+      aStatusType = this._statusType;
+    if (aStatusText === undefined || aStatusText === null)
+      aStatusText = this._statusText;
+    if (aAvailabilityDetails === undefined || aAvailabilityDetails === null)
+      aAvailabilityDetails = this._availabilityDetails;
+
+    // Decide which notifications should be fired.
+    let notifications = [];
+    if (this._statusType != aStatusType ||
+        this._availabilityDetails != aAvailabilityDetails)
+      notifications.push("availability-changed");
+    if (this._statusType != aStatusType ||
+        this._statusText != aStatusText) {
+      notifications.push("status-changed");
+      if (this.online && aStatusType <= Ci.imIStatusInfo.STATUS_OFFLINE)
+        notifications.push("signed-off");
+      if (!this.online && aStatusType > Ci.imIStatusInfo.STATUS_OFFLINE)
+        notifications.push("signed-on");
+    }
+
+    // Actually change the stored status.
+    [this._statusType, this._statusText, this._availabilityDetails] =
+      [aStatusType, aStatusText, aAvailabilityDetails];
+
+    // Fire the notifications.
+    notifications.forEach(function(aTopic) {
+      this._notifyObservers(aTopic);
+    }, this);
+  },
+
+  _availabilityDetails: 0,
+  get availabilityDetails() this._availabilityDetails,
+
+  get canSendMessage() this.online /*|| this.account.canSendOfflineMessage(this) */,
+
+  getTooltipInfo: function() EmptyEnumerator,
+  createConversation: function() { throw Cr.NS_ERROR_NOT_IMPLEMENTED; }
+};
+
+// aUserName is required only if aBuddy is null, i.e., we are adding a buddy.
+function AccountBuddy(aAccount, aBuddy, aTag, aUserName) {
+  this._init(aAccount, aBuddy, aTag, aUserName);
+}
+AccountBuddy.prototype = GenericAccountBuddyPrototype;
+
+const GenericMessagePrototype = {
+  __proto__: ClassInfo("prplIMessage", "generic message object"),
+  flags: Ci.nsIClassInfo.DOM_OBJECT,
+
+  _lastId: 0,
+  _init: function (aWho, aMessage, aObject) {
+    this.id = ++GenericMessagePrototype._lastId;
+    this.time = Math.round(new Date() / 1000);
+    this.who = aWho;
+    this.message = aMessage;
+    this.originalMessage = aMessage;
+
+    if (aObject)
+      for (let i in aObject)
+        this[i] = aObject[i];
+  },
+  _alias: "",
+  get alias() this._alias || this.who,
+  _iconURL: "",
+  get iconURL() {
+    // If the protocol plugin has explicitly set an icon for the message, use it.
+    if (this._iconURL)
+      return this._iconURL;
+
+    // Otherwise, attempt to find a buddy for incoming messages, and forward the call.
+    if (this.incoming && this._conversation && !this._conversation.isChat) {
+      let buddy = this._conversation.buddy;
+      if (buddy)
+        return buddy.buddyIconFilename;
+    }
+    return "";
+  },
+  _conversation: null,
+  get conversation() this._conversation,
+  set conversation(aConv) {
+    this._conversation = aConv;
+    aConv.notifyObservers(this, "new-text", null);
+  },
+
+  color: "",
+
+  outgoing: false,
+  incoming: false,
+  system: false,
+  autoResponse: false,
+  containsNick: false,
+  noLog: false,
+  error: false,
+  delayed: false,
+  noFormat: false,
+  containsImages: false,
+  notification: false,
+  noLinkification: false,
+
+  getActions: function(aCount) {
+    if (aCount)
+      aCount.value = 0;
+    return [];
+  }
+};
+
+function Message(aWho, aMessage, aObject) {
+  this._init(aWho, aMessage, aObject);
+}
+Message.prototype = GenericMessagePrototype;
+
+
+const GenericConversationPrototype = {
+  __proto__: ClassInfo("prplIConversation", "generic conversation object"),
+  flags: Ci.nsIClassInfo.DOM_OBJECT,
+  get wrappedJSObject() this,
+
+  _init: function(aAccount, aName) {
+    this._account = aAccount;
+    this._name = aName;
+    this._observers = [];
+    Services.conversations.addConversation(this);
+  },
+
+  _id: 0,
+  get id() this._id,
+  set id(aId) {
+    if (this._id)
+      throw Cr.NS_ERROR_ALREADY_INITIALIZED;
+    this._id = aId;
+  },
+
+  addObserver: function(aObserver) {
+    if (this._observers.indexOf(aObserver) == -1)
+      this._observers.push(aObserver);
+  },
+  removeObserver: function(aObserver) {
+    this._observers = this._observers.filter(function(o) o !== aObserver);
+  },
+  notifyObservers: function(aSubject, aTopic, aData) {
+    for each (let observer in this._observers)
+      observer.observe(aSubject, aTopic, aData);
+  },
+
+  sendMsg: function (aMsg) {
+    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+  },
+  sendTyping: function(aLength) { },
+
+  close: function() {
+    Services.obs.notifyObservers(this, "closing-conversation", null);
+    Services.conversations.removeConversation(this);
+  },
+  unInit: function() {
+    delete this._account;
+    delete this._observers;
+  },
+
+  writeMessage: function(aWho, aText, aProperties) {
+    (new Message(aWho, aText, aProperties)).conversation = this;
+  },
+
+  get account() this._account.imAccount,
+  get name() this._name,
+  get normalizedName() normalize(this.name),
+  get title() this.name
+};
+
+const GenericConvIMPrototype = {
+  __proto__: GenericConversationPrototype,
+  _interfaces: [Ci.prplIConversation, Ci.prplIConvIM],
+  classDescription: "generic ConvIM object",
+
+  updateTyping: function(aState) {
+    if (aState == this.typingState)
+      return;
+
+    if (aState == Ci.prplIConvIM.NOT_TYPING)
+      delete this.typingState;
+    else
+      this.typingState = aState;
+    this.notifyObservers(null, "update-typing", null);
+  },
+
+  get isChat() false,
+  buddy: null,
+  typingState: Ci.prplIConvIM.NOT_TYPING
+};
+
+const GenericConvChatPrototype = {
+  __proto__: GenericConversationPrototype,
+  _interfaces: [Ci.prplIConversation, Ci.prplIConvChat],
+  classDescription: "generic ConvChat object",
+
+  _nick: null,
+  _topic: null,
+  _topicSetter: null,
+  setTopic: function(aTopic, aTopicSetter) {
+    // Only change the topic if the topic and/or topic setter has changed.
+    if (this._topic == aTopic && this._topicSetter == aTopicSetter)
+      return;
+
+    this._topic = aTopic;
+    this._topicSetter = aTopicSetter;
+
+    this.notifyObservers(null, "chat-update-topic");
+  },
+
+  _init: function(aAccount, aName, aNick) {
+    this._participants = {};
+    this._nick = aNick;
+    GenericConversationPrototype._init.call(this, aAccount, aName);
+  },
+
+  get isChat() true,
+  get nick() this._nick,
+  get topic() this._topic,
+  get topicSetter() this._topicSetter,
+  get topicSettable() false,
+  get left() false,
+
+  getParticipants: function() {
+    return new nsSimpleEnumerator(
+      Object.keys(this._participants)
+            .map(function(key) this._participants[key], this)
+    );
+  },
+  getNormalizedChatBuddyName: function(aChatBuddyName) aChatBuddyName,
+
+  writeMessage: function (aWho, aText, aProperties) {
+    aProperties.containsNick = aText.indexOf(this.nick) != -1;
+    GenericConversationPrototype.writeMessage.apply(this, arguments);
+  }
+};
+
+const GenericConvChatBuddyPrototype = {
+  __proto__: ClassInfo("prplIConvChatBuddy", "generic ConvChatBuddy object"),
+
+  _name: "",
+  get name() this._name,
+  alias: "",
+  buddy: false,
+
+  get noFlags() !(this.voiced || this.halfOp || this.op ||
+                  this.founder || this.typing),
+  voiced: false,
+  halfOp: false,
+  op: false,
+  founder: false,
+  typing: false
+};
+
+function TooltipInfo(aLabel, aValue)
+{
+  if (aLabel === undefined)
+    this.type = Ci.prplITooltipInfo.sectionBreak;
+  else {
+    this.label = aLabel;
+    if (aValue === undefined)
+      this.type = Ci.prplITooltipInfo.sectionHeader;
+    else {
+      this.type = Ci.prplITooltipInfo.pair;
+      this.value = aValue;
+    }
+  }
+}
+TooltipInfo.prototype = ClassInfo("prplITooltipInfo", "generic tooltip info");
+
+/* aOption is an object containing:
+ *  - label: localized text to display (recommended: use a getter with _)
+ *  - default: the default value for this option. The type of the
+ *      option will be determined based on the type of the default value.
+ *      If the default value is a string, the option will be of type
+ *      list if listValues has been provided. In that case the default
+ *      value should be one of the listed values.
+ *  - [optional] listValues: only if this option can only take a list of
+ *      predefined values. This is an object of the form:
+ *        {value1: localizedLabel, value2: ...}.
+ *  - [optional] masked: boolean, if true the UI shouldn't display the value.
+ *      This could typically be used for password field.
+ *      Warning: The UI currently doesn't support this.
+ */
+function purplePref(aName, aOption) {
+  this.name = aName; // Preference name
+  this.label = aOption.label; // Text to display
+
+  if (aOption.default === undefined || aOption.default === null)
+    throw "A default value for the option is required to determine its type.";
+  this._defaultValue = aOption.default;
+
+  const kTypes = {boolean: "Bool", string: "String", number: "Int"};
+  let type = kTypes[typeof aOption.default];
+  if (!type)
+    throw "Invalid option type";
+
+  if (type == "String" && ("listValues" in aOption)) {
+    type = "List";
+    this._listValues = aOption.listValues;
+  }
+  this.type = Ci.prplIPref["type" + type];
+
+  if ("masked" in aOption && aOption.masked)
+    this.masked = true;
+}
+purplePref.prototype = {
+  __proto__: ClassInfo("prplIPref", "generic account option preference"),
+
+  masked: false,
+
+  // Default value
+  getBool: function() this._defaultValue,
+  getInt: function() this._defaultValue,
+  getString: function() this._defaultValue,
+  getList: function() {
+    // Convert a JavaScript object map {"value 1": "label 1", ...}
+    let keys = Object.keys(this._listValues);
+    if (!keys.length)
+      return EmptyEnumerator;
+
+    return new nsSimpleEnumerator(
+      keys.map(function(key) new purpleKeyValuePair(this[key], key),
+               this._listValues)
+    );
+  },
+  getListDefault: function() this._defaultValue
+};
+
+function purpleKeyValuePair(aName, aValue) {
+  this.name = aName;
+  this.value = aValue;
+}
+purpleKeyValuePair.prototype =
+  ClassInfo("prplIKeyValuePair", "generic Key Value Pair");
+
+function UsernameSplit(aValues) {
+  this._values = aValues;
+}
+UsernameSplit.prototype = {
+  __proto__: ClassInfo("prplIUsernameSplit", "username split object"),
+
+  get label() this._values.label,
+  get separator() this._values.separator,
+  get defaultValue() this._values.defaultValue,
+  get reverse() !!this._values.reverse // Ensure boolean
+};
+
+function ChatRoomField(aIdentifier, aField) {
+  this.identifier = aIdentifier;
+  this.label = aField.label;
+  this.required = !!aField.required;
+
+  let type = "TEXT";
+  if ((typeof aField.default) == "number") {
+    type = "INT";
+    this.min = aField.min;
+    this.max = aField.max;
+  }
+  else if (aField.isPassword)
+    type = "PASSWORD";
+  this.type = Ci.prplIChatRoomField["TYPE_" + type];
+}
+ChatRoomField.prototype =
+  ClassInfo("prplIChatRoomField", "ChatRoomField object");
+
+function ChatRoomFieldValues(aMap) {
+  this.values = aMap;
+}
+ChatRoomFieldValues.prototype = {
+  __proto__: ClassInfo("prplIChatRoomFieldValues", "ChatRoomFieldValues"),
+
+  getValue: function(aIdentifier)
+    this.values.hasOwnProperty(aIdentifier) ? this.values[aIdentifier] : null,
+  setValue: function(aIdentifier, aValue) {
+    this.values[aIdentifier] = aValue;
+  }
+};
+
+// the name getter and the getAccount method need to be implemented by
+// protocol plugins.
+const GenericProtocolPrototype = {
+  __proto__: ClassInfo("prplIProtocol", "Generic protocol object"),
+
+  init: function(aId) {
+    if (aId != this.id)
+      throw "Creating an instance of " + aId + " but this object implements " + this.id;
+  },
+  get id() "prpl-" + this.normalizedName,
+  get normalizedName() normalize(this.name),
+  get iconBaseURI() "chrome://chat/skin/prpl-generic/",
+
+  getAccount: function(aImAccount) { throw Cr.NS_ERROR_NOT_IMPLEMENTED; },
+
+  _getOptionDefault: function(aName) {
+    if (this.options && this.options.hasOwnProperty(aName))
+      return this.options[aName].default;
+    throw aName + " has no default value in " + this.id + ".";
+  },
+  getOptions: function() {
+    if (!this.options)
+      return EmptyEnumerator;
+
+    let purplePrefs = [];
+    for (let [name, option] in Iterator(this.options))
+      purplePrefs.push(new purplePref(name, option));
+    return new nsSimpleEnumerator(purplePrefs);
+  },
+  getUsernameSplit: function() {
+    if (!this.usernameSplits || !this.usernameSplits.length)
+      return EmptyEnumerator;
+
+    return new nsSimpleEnumerator(
+      this.usernameSplits.map(function(split) new UsernameSplit(split)));
+  },
+
+  registerCommands: function() {
+    if (!this.commands)
+      return;
+
+    this.commands.forEach(function(command) {
+      if (!command.hasOwnProperty("name") || !command.hasOwnProperty("run"))
+        throw "Every command must have a name and a run function.";
+      if (!command.hasOwnProperty("usageContext"))
+        command.usageContext = Ci.imICommand.CONTEXT_ALL;
+      if (!command.hasOwnProperty("priority"))
+        command.priority = Ci.imICommand.PRIORITY_PRPL;
+      Services.cmd.registerCommand(command, this.id);
+    }, this);
+  },
+
+  // NS_ERROR_XPC_JSOBJECT_HAS_NO_FUNCTION_NAMED errors are too noisy
+  get usernameEmptyText() "",
+  accountExists: function() false, //FIXME
+
+  get uniqueChatName() false,
+  get chatHasTopic() false,
+  get noPassword() false,
+  get newMailNotification() false,
+  get imagesInIM() false,
+  get passwordOptional() false,
+  get usePointSize() true,
+  get registerNoScreenName() false,
+  get slashCommandsNative() false,
+  get usePurpleProxy() false,
+
+  get classDescription() this.name + " Protocol",
+  get contractID() "@mozilla.org/chat/" + this.normalizedName + ";1"
+};
+
+function ForwardAccount(aBaseAccount)
+{
+  this._init(aBaseAccount);
+}
+ForwardAccount.prototype = ForwardAccountPrototype;
+
+// the baseId property should be set to the prpl id of the base protocol plugin
+// and the name getter is required.
+const ForwardProtocolPrototype = {
+  __proto__: GenericProtocolPrototype,
+
+  get base() {
+    if (!this.hasOwnProperty("_base"))
+      this._base = Services.core.getProtocolById(this.baseId);
+    return this._base;
+  },
+  getAccount: function(aImAccount)
+    new ForwardAccount(this.base.getAccount(aImAccount)),
+
+  get iconBaseURI() this.base.iconBaseURI,
+  getOptions: function() this.base.getOptions(),
+  getUsernameSplit: function() this.base.getUsernameSplit(),
+  accountExists: function(aName) this.base.accountExists(aName),
+  get uniqueChatName() this.base.uniqueChatName,
+  get chatHasTopic() this.base.chatHasTopic,
+  get noPassword() this.base.noPassword,
+  get newMailNotification() this.base.newMailNotification,
+  get imagesInIM() this.base.imagesInIM,
+  get passwordOptional() this.base.passwordOptional,
+  get usePointSize() this.base.usePointSize,
+  get registerNoScreenName() this.base.registerNoScreenName,
+  get slashCommandsNative() this.base.slashCommandsNative,
+  get usePurpleProxy() this.base.usePurpleProxy,
+
+  registerCommands: function() {
+    // Get the base protocol's commands and re-register them for this protocol.
+    for each (let command in Services.cmd.listCommandsForProtocol(this.baseId))
+      Services.cmd.registerCommand(command, this.id);
+  }
+};
new file mode 100644
--- /dev/null
+++ b/chat/modules/socket.jsm
@@ -0,0 +1,535 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is JSIRC Library
+ *
+ * The Initial Developer of the Original Code is New Dimensions Consulting, Inc.
+ * Portions created by the Initial Developer are Copyright (C) 1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Robert Ginda, <rginda@ndcico.com>, original author
+ *   Peter Van der Beken, <peter.vanderbeken@pandora.be>, necko-only version
+ *   Stephen Clavering <mozilla@clav.me.uk>, extensively rewritten for
+ *     MSNMessenger (http://msnmsgr.mozdev.org/)
+ *   Benoît Renard <benoit@gawab.com>, MSNMessenger
+ *   Patrick Cloke, <clokep@gmail.com>, updated, extended and generalized for
+ *     Instantbird (http://www.instantbird.com)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * Combines a lot of the Mozilla networking interfaces into a sane interface for
+ * simple(r) use of sockets code.
+ *
+ * This implements nsIServerSocketListener, nsIStreamListener,
+ * nsIRequestObserver, nsITransportEventSink, nsIBadCertListener2,
+ * nsISSLErrorListener, and nsIProtocolProxyCallback.
+ *
+ * This uses nsISocketTransportServices, nsIServerSocket, nsIThreadManager,
+ * nsIBinaryInputStream, nsIScriptableInputStream, nsIInputStreamPump,
+ * nsIProxyService, nsIProxyInfo.
+ *
+ * High-level methods:
+ *   .connect(<host>, <port>[, ("starttls" | "ssl" | "udp") [, <proxy>]])
+ *   .disconnect()
+ *   .reconnect()
+ *   .listen(port)
+ *   .send(String data)
+ *   .isAlive()
+ *   .startTLS()
+ * High-level properties:
+ *   XXX Need to include properties here
+ *
+ * Users should "subclass" this object, i.e. set their .__proto__ to be it. And
+ * then implement:
+ *   onConnection()
+ *   onConnectionHeard()
+ *   onConnectionTimedOut()
+ *   onConnectionReset()
+ *   onConnectionClosed()
+ *   onDataReceived(data)
+ *   onBinaryDataReceived(ArrayBuffer data, int length)
+ *   onTransportStatus(nsISocketTransport transport, nsresult status,
+ *                     unsigned long progress, unsigned longprogressMax)
+ *   log(message)
+ */
+
+/*
+ * To Do:
+ *   Add a message queue to keep from flooding a server (just an array, just
+ *     keep shifting the first element off and calling as setTimeout for the
+ *     desired flood time?).
+ */
+
+const EXPORTED_SYMBOLS = ["Socket"];
+
+const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
+Cu.import("resource://gre/modules/Services.jsm");
+
+// Network errors see: netwerk/base/public/nsNetError.h
+const NS_ERROR_MODULE_NETWORK = 2152398848;
+const NS_ERROR_CONNECTION_REFUSED = NS_ERROR_MODULE_NETWORK + 13;
+const NS_ERROR_NET_TIMEOUT = NS_ERROR_MODULE_NETWORK + 14;
+const NS_ERROR_NET_RESET = NS_ERROR_MODULE_NETWORK + 20;
+const NS_ERROR_UNKNOWN_HOST = NS_ERROR_MODULE_NETWORK + 30;
+const NS_ERROR_UNKNOWN_PROXY_HOST = NS_ERROR_MODULE_NETWORK + 42;
+const NS_ERROR_PROXY_CONNECTION_REFUSED = NS_ERROR_MODULE_NETWORK + 72;
+
+const BinaryInputStream =
+  Components.Constructor("@mozilla.org/binaryinputstream;1",
+                         "nsIBinaryInputStream", "setInputStream");
+const BinaryOutputStream =
+  Components.Constructor("@mozilla.org/binaryoutputstream;1",
+                         "nsIBinaryOutputStream", "setOutputStream");
+const ScriptableInputStream =
+  Components.Constructor("@mozilla.org/scriptableinputstream;1",
+                         "nsIScriptableInputStream", "init");
+const ServerSocket =
+  Components.Constructor("@mozilla.org/network/server-socket;1",
+                         "nsIServerSocket", "init");
+const InputStreamPump =
+  Components.Constructor("@mozilla.org/network/input-stream-pump;1",
+                         "nsIInputStreamPump", "init");
+const ScriptableUnicodeConverter =
+  Components.Constructor("@mozilla.org/intl/scriptableunicodeconverter",
+                         "nsIScriptableUnicodeConverter");
+
+const Socket = {
+  // Use this to use binary mode for the
+  binaryMode: false,
+
+  // Set this for non-binary mode to automatically parse the stream into chunks
+  // separated by delimiter.
+  delimiter: "",
+
+  // Set this for binary mode to split after a certain number of bytes have
+  // been received.
+  inputSegmentSize: 0,
+
+  // Set this for the segment size of outgoing binary streams.
+  outputSegmentSize: 0,
+
+  // Flags used by nsIProxyService when resolving a proxy.
+  proxyFlags: Ci.nsIProtocolProxyService.RESOLVE_PREFER_SOCKS_PROXY,
+
+  // Time (in seconds) for nsISocketTransport to continue trying before
+  // reporting a failure, 0 is forever.
+  connectTimeout: 0,
+  readWriteTimeout: 0,
+
+  /*
+   *****************************************************************************
+   ******************************* Public methods ******************************
+   *****************************************************************************
+   */
+  // Synchronously open a connection.
+  connect: function(aHost, aPort, aSecurity, aProxy) {
+    this.log("Connecting to: " + aHost + ":" + aPort);
+    this.host = aHost;
+    this.port = aPort;
+
+    // Array of security options
+    this.security = aSecurity || [];
+
+    // Choose a proxy, use the given one, otherwise get one from the proxy
+    // service
+    if (aProxy)
+      this._createTransport(aProxy);
+    else {
+      try {
+        // Attempt to get a default proxy from the proxy service.
+        let proxyService = Cc["@mozilla.org/network/protocol-proxy-service;1"]
+                              .getService(Ci.nsIProtocolProxyService);
+
+        // Add a URI scheme since, by default, some protocols (i.e. IRC) don't
+        // have a URI scheme before the host.
+        let uri = Services.io.newURI("http://" + this.host, null, null);
+        this._proxyCancel = proxyService.asyncResolve(uri, this.proxyFlags, this);
+      } catch(e) {
+        Cu.reportError(e);
+        // We had some error getting the proxy service, just don't use one.
+        this._createTransport(null);
+      }
+    }
+  },
+
+  // Reconnect to the current settings stored in the socket.
+  reconnect: function() {
+    // If there's nothing to reconnect to or we're connected, do nothing
+    if (!this.isAlive() && this.host && this.port) {
+      this.disconnect();
+      this.connect(this.host, this.port, this.security, this.proxy);
+    }
+  },
+
+  // Disconnect all open streams.
+  disconnect: function() {
+    this.log("Disconnect");
+
+    // Close all input and output streams.
+    if ("_inputStream" in this) {
+      this._inputStream.close();
+      delete this._inputStream;
+    }
+    if ("_outputStream" in this) {
+      this._outputStream.close();
+      delete this._outputStream;
+    }
+    if ("transport" in this) {
+      this.transport.close(Cr.NS_OK);
+      delete this.transport;
+    }
+
+    if ("_proxyCancel" in this) {
+      this._proxyCancel.cancel(Cr.NS_ERROR_ABORT); // Has to give a failure code
+      delete this._proxyCancel;
+    }
+  },
+
+  // Listen for a connection on a port.
+  // XXX take a timeout and then call stopListening
+  listen: function(port) {
+    this.log("Listening on port " + port);
+
+    this.serverSocket = new ServerSocket(port, false, -1);
+    this.serverSocket.asyncListen(this);
+  },
+
+  // Stop listening for a connection.
+  stopListening: function() {
+    this.log("Stop listening");
+    // Close the socket to stop listening.
+    if ("serverSocket" in this)
+      this.serverSocket.close();
+  },
+
+  // Send data on the output stream. Provide aLoggedData to log something
+  // different than what is actually sent.
+  sendData: function(/* string */ aData, aLoggedData) {
+    this.log("Sending:\n" + (aLoggedData || aData));
+
+    try {
+      this._outputStream.write(aData + this.delimiter,
+                               aData.length + this.delimiter.length);
+    } catch(e) {
+      Cu.reportError(e);
+    }
+  },
+
+  // Send a string to the output stream after converting the encoding. Provide
+  // aLoggedData to log something different than what is actually sent.
+  sendString: function(aString, aEncoding, aLoggedData) {
+    this.log("Sending:\n" + (aLoggedData || aString));
+
+    let converter = new ScriptableUnicodeConverter();
+    converter.charset = aEncoding || "UTF-8";
+    try {
+      let stream = converter.convertToInputStream(aString + this.delimiter);
+      this._outputStream.writeFrom(stream, stream.available());
+    } catch(e) {
+      Cu.reportError(e);
+    }
+  },
+
+  sendBinaryData: function(/* ArrayBuffer */ aData) {
+    this.log("Sending binary data data: <" + aData + ">");
+
+    let uint8 = Uint8Array(aData);
+
+    // Since there doesn't seem to be a uint8.get() method for the byte array
+    let byteArray = [];
+    for (let i = 0; i < uint8.byteLength; i++)
+      byteArray.push(uint8[i]);
+    try {
+      // Send the data as a byte array
+      this._binaryOutputStream.writeByteArray(byteArray, byteArray.length);
+    } catch(e) {
+      Cu.reportError(e);
+    }
+  },
+
+  isAlive: function() {
+    if (!this.transport)
+      return false;
+    return this.transport.isAlive();
+  },
+
+  startTLS: function() {
+    this.transport.securityInfo.QueryInterface(Ci.nsISSLSocketControl).StartTLS();
+  },
+
+  /*
+   *****************************************************************************
+   ***************************** Interface methods *****************************
+   *****************************************************************************
+   */
+  /*
+   * nsIProtocolProxyCallback methods
+   */
+  onProxyAvailable: function(aRequest, aURI, aProxyInfo, aStatus) {
+    if (aProxyInfo) {
+      this.log("using " + aProxyInfo.type + " proxy: " +
+               aProxyInfo.host + ":" + aProxyInfo.port);
+    }
+    this._createTransport(aProxyInfo);
+    delete this._proxyCancel;
+  },
+
+  /*
+   * nsIServerSocketListener methods
+   */
+  // Called after a client connection is accepted when we're listening for one.
+  onSocketAccepted: function(aServerSocket, aTransport) {
+    this.log("onSocketAccepted");
+    // Store the values
+    this.transport = aTransport;
+    this.host = this.transport.host;
+    this.port = this.transport.port;
+
+    this._resetBuffers();
+    this._openStreams();
+
+    this.onConnectionHeard();
+    this.stopListening();
+  },
+  // Called when the listening socket stops for some reason.
+  // The server socket is effectively dead after this notification.
+  onStopListening: function(aSocket, aStatus) {
+    this.log("onStopListening");
+    if ("serverSocket" in this)
+      delete this.serverSocket;
+  },
+
+  /*
+   * nsIStreamListener methods
+   */
+  // onDataAvailable, called by Mozilla's networking code.
+  // Buffers the data, and parses it into discrete messages.
+  onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, aCount) {
+    if (this.binaryMode) {
+      // Load the data from the stream
+      this._incomingDataBuffer = this._incomingDataBuffer
+                                     .concat(this._binaryInputStream
+                                                 .readByteArray(aCount));
+
+      let size = this.inputSegmentSize || this._incomingDataBuffer.length;
+      this.log(size + " " + this._incomingDataBuffer.length);
+      while (this._incomingDataBuffer.length >= size) {
+        let buffer = new ArrayBuffer(size);
+
+        // Create a new ArraybufferView
+        let uintArray = new Uint8Array(buffer);
+
+        // Set the data into the array while saving the extra data
+        uintArray.set(this._incomingDataBuffer.splice(0, size));
+
+        // Notify we've received data
+        this.onBinaryDataReceived(buffer);
+      }
+    } else {
+      if (this.delimiter) {
+        // Load the data from the stream
+        this._incomingDataBuffer += this._scriptableInputStream.read(aCount);
+        let data = this._incomingDataBuffer.split(this.delimiter);
+
+        // Store the (possibly) incomplete part
+        this._incomingDataBuffer = data.pop();
+
+        // Send each string to the handle data function
+        data.forEach(this.onDataReceived, this);
+      } else {
+        // Send the whole string to the handle data function
+        this.onDataReceived(this._scriptableInputStream.read(aCount));
+      }
+    }
+  },
+
+  /*
+   * nsIRequestObserver methods
+   */
+  // Signifies the beginning of an async request
+  onStartRequest: function(aRequest, aContext) {
+    this.log("onStartRequest");
+  },
+  // Called to signify the end of an asynchronous request.
+  onStopRequest: function(aRequest, aContext, aStatus) {
+    this.log("onStopRequest (" + aStatus + ")");
+    if (aStatus == NS_ERROR_NET_RESET)
+      this.onConnectionReset();
+    else if (aStatus == NS_ERROR_NET_TIMEOUT)
+      this.onConnectionTimedOut();
+    else
+      this.onConnectionClosed();
+  },
+
+  /*
+   * nsIBadCertListener2
+   */
+  // Called when there's an error, return true to suppress the modal alert.
+  // Whatever this function returns, NSS will close the connection.
+  notifyCertProblem: function(aSocketInfo, aStatus, aTargetSite) true,
+
+  /*
+   * nsISSLErrorListener
+   */
+  notifySSLError: function(aSocketInfo, aError, aTargetSite) true,
+
+  /*
+   * nsITransportEventSink methods
+   */
+  onTransportStatus: function(aTransport, aStatus, aProgress, aProgressmax) {
+    const nsITransportEventSinkStatus = {
+         0x804b0003: "STATUS_RESOLVING",
+         0x804b000b: "STATUS_RESOLVED",
+         0x804b0007: "STATUS_CONNECTING_TO",
+         0x804b0004: "STATUS_CONNECTED_TO",
+         0x804b0005: "STATUS_SENDING_TO",
+         0x804b000a: "STATUS_WAITING_FOR",
+         0x804b0006: "STATUS_RECEIVING_FROM"
+    };
+    let status = nsITransportEventSinkStatus[aStatus];
+    this.log("onTransportStatus(" + (status || ("0x" + aStatus.toString(16))) +")");
+
+    if (status == "STATUS_CONNECTED_TO") {
+      // Notify that the connection has been established.
+      this.onConnection();
+    }
+  },
+
+  /*
+   *****************************************************************************
+   ****************************** Private methods ******************************
+   *****************************************************************************
+   */
+  _resetBuffers: function() {
+    this._incomingDataBuffer = this.binaryMode ? [] : "";
+    this._outgoingDataBuffer = [];
+  },
+
+  _createTransport: function(aProxy) {
+    this.proxy = aProxy;
+
+    // Empty incoming and outgoing data storage buffers
+    this._resetBuffers();
+
+    // Create a socket transport
+    let socketTS = Cc["@mozilla.org/network/socket-transport-service;1"]
+                      .getService(Ci.nsISocketTransportService);
+    this.transport = socketTS.createTransport(this.security,
+                                              this.security.length, this.host,
+                                              this.port, this.proxy);
+
+    this._openStreams();
+  },
+
+  // Open the incoming and outgoing streams, and init the nsISocketTransport.
+  _openStreams: function() {
+    // Security notification callbacks (must support nsIBadCertListener2 and
+    // nsISSLErrorListener for SSL connections, and possibly other interfaces).
+    this.transport.securityCallbacks = this;
+
+    // Set the timeouts for the nsISocketTransport for both a connect event and
+    // a read/write. Only set them if the user has provided them.
+    if (this.connectTimeout) {
+      this.transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT,
+                                this.connectTimeout);
+    }
+    if (this.readWriteTimeout) {
+      this.transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_READ_WRITE,
+                                this.readWriteTimeout);
+    }
+
+    this.transport.setEventSink(this, Services.tm.currentThread);
+
+    // No limit on the output stream buffer
+    this._outputStream = this.transport.openOutputStream(0, // flags
+                                                         this.outputSegmentSize, // Use default segment size
+                                                         -1); // Segment count
+    if (!this._outputStream)
+      throw "Error getting output stream.";
+
+    this._inputStream = this.transport.openInputStream(0, // flags
+                                                       0, // Use default segment size
+                                                       0); // Use default segment count
+    if (!this._inputStream)
+      throw "Error getting input stream.";
+
+    if (this.binaryMode) {
+      // Handle binary mode
+      this._binaryInputStream = new BinaryInputStream(this._inputStream);
+      this._binaryOutputStream = new BinaryOutputStream(this._outputStream);
+    } else {
+      // Handle character mode
+      this._scriptableInputStream =
+        new ScriptableInputStream(this._inputStream);
+    }
+
+    this.pump = new InputStreamPump(this._inputStream, // Data to read
+                                    -1, // Current offset
+                                    -1, // Read all data
+                                    0, // Use default segment size
+                                    0, // Use default segment length
+                                    false); // Do not close when done
+    this.pump.asyncRead(this, this);
+  },
+
+  /*
+   *****************************************************************************
+   ********************* Methods for subtypes to override **********************
+   *****************************************************************************
+   */
+  log: function(aString) { },
+  // Called when a connection is established.
+  onConnection: function() { },
+  // Called when a socket is accepted after listening.
+  onConnectionHeard: function() { },
+  // Called when a connection times out.
+  onConnectionTimedOut: function() { },
+  // Called when a socket request's network is reset.
+  onConnectionReset: function() { },
+  // Called when the other end has closed the connection.
+  onConnectionClosed: function() { },
+
+  // Called when ASCII data is available.
+  onDataReceived: function(/* string */ aData) { },
+
+  // Called when binary data is available.
+  onBinaryDataReceived: function(/* ArrayBuffer */ aData) { },
+
+  /* QueryInterface and nsIInterfaceRequestor implementations */
+  _interfaces: [Ci.nsIServerSocketListener, Ci.nsIStreamListener,
+                Ci.nsIRequestObserver, Ci.nsITransportEventSink,
+                Ci.nsIBadCertListener2, Ci.nsISSLErrorListener,
+                Ci.nsIProtocolProxyCallback],
+  QueryInterface: function(iid) {
+    if (iid.equals(Ci.nsISupports) ||
+        this._interfaces.some(function(i) i.equals(iid)))
+      return this;
+
+    throw Cr.NS_ERROR_NO_INTERFACE;
+  },
+  getInterface: function(iid) this.QueryInterface(iid)
+};
new file mode 100644
--- /dev/null
+++ b/chat/protocols/facebook/Makefile.in
@@ -0,0 +1,49 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is
+# Florian Quèze <florian@queze.net>.
+# Portions created by the Initial Developer are Copyright (C) 2011
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either of the GNU General Public License Version 2 or later (the "GPL"),
+# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH		= ../../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+EXTRA_COMPONENTS = \
+		facebook.js \
+		facebook.manifest \
+		$(NULL)
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/chat/protocols/facebook/facebook.js
@@ -0,0 +1,87 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Instantbird.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian Quèze <florian@queze.net>.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const {interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource:///modules/imXPCOMUtils.jsm");
+Cu.import("resource:///modules/jsProtoHelper.jsm");
+Cu.import("resource:///modules/xmpp.jsm");
+Cu.import("resource:///modules/xmpp-session.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "_", function()
+  l10nHelper("chrome://chat/locale/facebook.properties")
+);
+
+function FacebookAccount(aProtoInstance, aImAccount) {
+  this._init(aProtoInstance, aImAccount);
+}
+FacebookAccount.prototype = {
+  __proto__: XMPPAccountPrototype,
+  get canJoinChat() false,
+  connect: function() {
+    if (this.name.indexOf("@") == -1) {
+      let jid = this.name + "@chat.facebook.com/" + XMPPDefaultResource;
+      this._jid = this._parseJID(jid);
+    }
+    else {
+      this._jid = this._parseJID(this.name);
+      if (this._jid.domain != "chat.facebook.com") {
+        // We can't use this.onError because this._connection doesn't exist.
+        this.reportDisconnecting(Ci.prplIAccount.ERROR_INVALID_USERNAME,
+                                 _("connection.error.useUsernameNotEmailAddress"));
+        this.reportDisconnected();
+        return;
+      }
+    }
+
+    this._connection = new XMPPSession("chat.facebook.com", 5222,
+                                       "opportunistic_tls", this._jid,
+                                       this.imAccount.password, this);
+  }
+};
+
+function FacebookProtocol() {
+}
+FacebookProtocol.prototype = {
+  __proto__: GenericProtocolPrototype,
+  get normalizedName() "facebook",
+  get name() "Facebook Chat",
+  get iconBaseURI() "chrome://prpl-facebook/skin/",
+  getAccount: function(aImAccount) new FacebookAccount(this, aImAccount),
+  classID: Components.ID("{1d1d0bc5-610c-472f-b2cb-4b89857d80dc}")
+};
+
+const NSGetFactory = XPCOMUtils.generateNSGetFactory([FacebookProtocol]);
new file mode 100644
--- /dev/null
+++ b/chat/protocols/facebook/facebook.manifest
@@ -0,0 +1,3 @@
+component {1d1d0bc5-610c-472f-b2cb-4b89857d80dc} facebook.js
+contract @mozilla.org/chat/facebook;1 {1d1d0bc5-610c-472f-b2cb-4b89857d80dc}
+category im-protocol-plugin prpl-facebook @mozilla.org/chat/facebook;1
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..77e6d358b62d50924610584a77442871038a9057
GIT binary patch
literal 1193
zc$@*L1XlZrP)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F800001b5ch_0Itp)
z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01ejw01ejxLMWSf00007bV*G`2iXJ>
z2{;vzQxf6;00b#XL_t(o!^M|RXdP7)#(($D%zMw5KTXp#Nn=~17(>-XP}Eiw6?IXI
z5Cow+7j9g*3)F=uZd?>`qiZ+rv<jt*>O!e*tP~XrT3Ss1rLCo=ttF|=yqSB?aq;H8
zCYhJa1Ri)F!*CBX_xtX5zH{b2_@9Sa9T?wxaJP-@j5<H9XxKd<<4hPFmQ+5z@Xb3v
zcO)>r_u%eIHTZsFa-z0l$C#~FA}XOJu!bnKgchuA&W!=ndRxIOcrSPX=LIi9>LGPP
zBlX0Ml)tWAmGiT+u}kH}7Ks%*w1J&@@S%y?z}8WF?%ER0-|3pvfi(;akJ@q7+POc^
z&H%6H2&lN}?e~t@3-d8)>bsh2=i%0(ux)I_o;`DBx)onZ0K!TXk*0T0aitgc0{!)f
zH(q{#rzW?sV{09g<wsRnN<91CJBD*1l_)}&;pR*h8b!sSs-+1G_C|dD{&NJ@<jrRc
z^=jB$PaL6@m3XUPuvQQeP$^B|t$hy@Sc9szcfNY4c+^;;RV4ESO!HwVufXnI!|mNd
z)A^qqKE*F*=IN_PJpITBssgHnmI4Azx+PPfM1f_Xx3VI7=*M#$`r#}=U_jyI>~$(p
zfH8)^==ubV3L@nyKtvhYd`~WZdHyzGklETm6dF_oRj@@0U?fXH@%ZV}*V;~Bp8Es)
zr#P^Gsy%=6qhp+&`zPxM1twJ0X1+GkX4f#@U8Ve*Yvu`vpt3On>$(4~u_g0Zfd@pI
zDJa?%z_V|E$=Fa2Uwp7{Mf~$`Px0B&Kicyo>#C{>T0p>ikE*9k5yT!ENu@*E^~8;2
z&B`et;4%eli89FVXjv{hq)HI))+dlUkBDH5mcUm;eMj(4rL3wrUr+_k`Ha0Osep6@
z>e$Lv@!qdbAW749-!7v-=Zewh_oRa|&J__zQbc^NV{OC(YbhY&an2Pf;6R1PCdXE*
z*AZxFQ#}_S9qeIhVmKH7?erDWLIO!M1&>c|E7gGdduqA(wxM30*fo}m|9Wb!xxw`a
z#Em3750q#Gx|TGZ<03*MNpfFblief0FU1KKW%klMTKE%mjdR25Cuc9s6Ih7j6u<)k
z*}=L6Uf=oByI&1#9(<uz3ryT_$OevOg{R`cvZcRH-0*t&%GG0Y-+lNh&<HaoHDGAr
z;)x?c_4#L%TGZE5F;N&0MFG_)APNJ*z_#z!8p6P0jLEL1>Z*!};zjVHIOj>dkR+ZP
zx0dwpn~Ukf#S=$>Uceim0t^5<fw8`wd-n9-_v9;4-^im-rXQF7W=gtv?e~R?Cyp-8
z9X}471m;@=HUZm#`+-rQo*C413q8A*fSbU1U=Fw*0s_Rq^$hSPP;DI#yEqg`f!n|h
z;9tPCOc-RK)n(v@?hnaG#6SZ`&9XnKwga%~vI4EUWS9O2b)7aZKM_5p00000NkvXX
Hu0mjf@!T{(
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2501acaab51de52c513812f2d531f4695abe5c2e
GIT binary patch
literal 1521
zc$@+81rGX&P)<h;3K|Lk000e1NJLTq001xm001xu1^@s6R|5Hm00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H11#U@1K~!jg)tTFC9Yq|+KQp^~a*~{+v6nQ~Hnq^Iy&$4e>g7SH
zPq7d6K@fcML3}O+!58tNf>!iD5TS@7DoBMY7R5eUf>_Z?3r!mm)Lvq1noD!e?#}#t
z*xlqLXU}DGwmtR-!|u-P?tFjq{r$c(yQ4&eyV-Q70kmswTIIHpqx-a{4@+nc3X*!y
zoic?AmFs+E1N;8?iPulpw66@IZ6inb6}p`7cRaYGxMj;G-Bs{#9gU+ET7lLoK36yz
zl#1$=QYZyVrJxY70s>eohzQ1l6~S7;#@EKmVhN4K7@^_^{QcKuJ3cm64g<S?r4u!y
zs~vrK`vW_Q0~?0)rK<~s=2n|y*q%9BF)%o!?-wavx^VF@@M3j)O%8|+4{pA1qn?^B
z6NU!FcC;8{n4Yb0@1~7fY<O@b-Wmo3+=Ay31f3oEY74DU@H`|1Z)$>SC5YJD0!Ast
z6Fb-O)UFNe+|tjw{w{ipF4hWV->~2trfyca@^6_VpPpTYDa}$Ha1;@RIoqz`HN5fC
z9yYJ<sqIrMJXh1#<1si;WcSuJ9Qow8C7^W#KV2eKq!fADqubZ=-m8z{=roy1w`vUa
z9!qBca%G^q%i+zJAI=1x6&Rc>4xq@DfdhLtGf*mIes0waDDq9>CwC9lZJWAT=Ge(G
z&QDG=J@0c*slf1HiHC*<mSNCYazHB}M+SBd57cct^6?qYj9p*6zIx*p=O?H6?&m+3
zV<LkJA^9=TTXZQFoYc1Ya=<SardoPW>Jo)|4v?P;mAbvUZC7qAwA@do8~|xsg}#1!
z1kcsz2FkN-sE=>o8L6rN@SAgd{^P}Ebt=n5mIni+ZV#{C>=T>TDwUyvt84nNHw>u`
zNRBzM8cXj#V-n}7LaVa7nSpc_%)o%;z`*`PU!as?Xl)Olz4d(E`<^@WC4pHEHZ-<9
z2COx0GvEg{K86kNs{{c-*u1ZLc<MNiUkEi-Gigup(nHABQn+es!GTuh9I+V~Fk*9L
zpz(X8#lA9ykTEv5rG(T`kHz*DL&(?+DrAk#?}Mh<d!)6#vN2%uW1v|Mw9J6X6hiIG
zXkyL4wVDGqOA@C|B&)WTRH%7hHcJkKCj8&2kTE8;K0SoOh=JE$c)UsHP5Y(P-S}hv
zl>?8|RTuz2FdRGaYt|4l@yN6vM)f`g6VnQ$!97|>EQNI#Nd4jnLzDZDtyP;uLQqeI
zG=N$%4RO4QT}rTaOA~;NpN*Cp2?I(goqz>!<CCOlfFNd|*(}=H05*|P8uydT6ICJP
z02UCX6mAu82k;^XA{DCmAzfbjFOe)RG;VIpQrQop99UYGcL4!l0IOpMoVcMEFuot6
zfz!WFQ1KhD=WnMiR02lNPU2{V9~ffLy)jk?P%hEvyFd}>xBlFz+1bLg#iFCmUA)2{
zW0%_o^6p1Jtom8U(X8w5Ve0yvSby#m&`(6G0c;XNE|FKInTgTkPS-O}3q`N5x1c=N
z!EqF><3yW%j>6FzM@RpBTt}l+yz!^i;yFr1i4|}DRh!6CY>UL3eaVKOwb;eYK1&c-
zu1_z>wHvd+%*5z%pahhG3J@d?Bw^%DUpP5#DsvzBTlbF4_HEn)s>6H9VuRVMXJ;lx
zPs~pKG)|PK4l!;86o76bYSsgTz=rs`7(b9Wc}J1QB_JB{8DI*y2K*CWFSzkdArU2W
zF<={?7hqATI$;SmAD9Pj#<(hRy|_eRLn1E~QF6=^<wTO?opd0<R{<7cY~{GlCUZdb
zJdl)5gHNp5iJ+<&Vh2{v#ECO2<GvF)P<_5AopBLBl!%b5D_9P?(<8sqr0OUDA|n3*
X#Oqz6I>X3;00000NkvXXu0mjfGmX70
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..bc42cf9b0b3251a61786d99628207d2978969cb2
GIT binary patch
literal 552
zc$@(!0@wYCP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10i{VqK~y-6m6N?|6hRoqf3rKg%itZFAgF~!4(vpWL!t!1-cPU+
zJ3&&3mBoQz8Pw83(%9J<@GlUXASz-jmSTOmnB-<ZX6CgxH+#7c<lbuDdEWWW^DvC+
z+<5U=L^ec%VS!RAvP!9*TDkswc<IWjSw7Lmq%m0%k?4pdRw$(qgb={_Kv4zq(ql_c
zf3U~*FJG+|3Fdg)@Z;CcA_nN3X(1Af21sK;f}1(G)K0m5`7Bq@pJL^7i~Zbj>)8iP
zGSW4Gj)dT61D@XN(mi()fStX9G>Hk`k!jTcBIW}wbd~{l@bV)+fA69ch>Tnx0u*5?
z!uG4XNBTEUt^u(2XlryZ9*ZEs&vh_a^<2c51LxcnV0Ha10G~JSAL;8-5*k2NR&!sW
zu1Vbo-vIKm{BMLwy{f7PFti21JJ#=X=K>zyym+|YeE*HSD8~Ug;vMoY*USiGns|q`
zMFTh(T0(HVe)nnW;H<s+(i&iQ{~Va5R>m=tVzLC2C1j~a#}RQHp@1NQ_d-!Q%F59n
q>;qV>pw}Pd8~s7P(02x`g5F=w(8@aM?(+5k0000<MNUMnLSTYh=Jh%N
new file mode 100644
--- /dev/null
+++ b/chat/protocols/facebook/jar.mn
@@ -0,0 +1,5 @@
+chat.jar:
+% skin prpl-facebook classic/1.0 %skin/classic/prpl/facebook/
+	skin/classic/prpl/facebook/icon32.png	(icons/prpl-facebook-32.png)
+	skin/classic/prpl/facebook/icon48.png	(icons/prpl-facebook-48.png)
+	skin/classic/prpl/facebook/icon.png	(icons/prpl-facebook.png)
new file mode 100644
--- /dev/null
+++ b/chat/protocols/gtalk/Makefile.in
@@ -0,0 +1,49 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is
+# Florian Quèze <florian@queze.net>.
+# Portions created by the Initial Developer are Copyright (C) 2011
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either of the GNU General Public License Version 2 or later (the "GPL"),
+# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH		= ../../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+EXTRA_COMPONENTS = \
+		gtalk.js \
+		gtalk.manifest \
+		$(NULL)
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/chat/protocols/gtalk/gtalk.js
@@ -0,0 +1,95 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Instantbird.
+ *
+ * The Initial Developer of the Original Code is
+ * Varuna JAYASIRI <vpjayasiri@gmail.com>.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Florian Quèze <florian@queze.net>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const Cu = Components.utils;
+
+Cu.import("resource:///modules/imXPCOMUtils.jsm");
+Cu.import("resource:///modules/jsProtoHelper.jsm");
+Cu.import("resource:///modules/xmpp.jsm");
+Cu.import("resource:///modules/xmpp-session.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "_", function()
+  l10nHelper("chrome://chat/locale/xmpp.properties")
+);
+
+function GTalkAccount(aProtoInstance, aImAccount) {
+  this._init(aProtoInstance, aImAccount);
+}
+GTalkAccount.prototype = {
+  __proto__: XMPPAccountPrototype,
+  connect: function() {
+    this._jid = this._parseJID(this.name);
+
+    // For the resource, if the user has edited the option to a non
+    // empty value, use that.
+    if (this.prefs.prefHasUserValue("resource")) {
+      let resource = this.getString("resource");
+      if (resource)
+        this._jid.resource = resource;
+    }
+    // Otherwise, if the username doesn't contain a resource, use the
+    // value of the resource option (it will be the default value).
+    // If we set an empty resource, XMPPSession will fallback to
+    // XMPPDefaultResource (set to brandShortName).
+    if (!this._jid.resource)
+      this._jid.resource = this.getString("resource");
+
+    this._connection =
+      new XMPPSession("talk.google.com", 443,
+                      "require_tls", this._jid,
+                      this.imAccount.password, this);
+  }
+};
+
+function GTalkProtocol() {
+}
+GTalkProtocol.prototype = {
+  __proto__: GenericProtocolPrototype,
+  get normalizedName() "gtalk",
+  get name() "Google Talk",
+  get iconBaseURI() "chrome://prpl-gtalk/skin/",
+  get usernameEmptyText() _("gtalk.usernameHint"),
+  getAccount: function(aImAccount) new GTalkAccount(this, aImAccount),
+  options: {
+    resource: {get label() _("options.resource"),
+               get default() XMPPDefaultResource}
+  },
+  get chatHasTopic() true,
+  classID: Components.ID("{38a224c1-6748-49a9-8ab2-efc362b1000d}")
+};
+
+const NSGetFactory = XPCOMUtils.generateNSGetFactory([GTalkProtocol]);
new file mode 100644
--- /dev/null
+++ b/chat/protocols/gtalk/gtalk.manifest
@@ -0,0 +1,3 @@
+component {38a224c1-6748-49a9-8ab2-efc362b1000d} gtalk.js
+contract @mozilla.org/chat/gtalk;1 {38a224c1-6748-49a9-8ab2-efc362b1000d}
+category im-protocol-plugin prpl-gtalk @mozilla.org/chat/gtalk;1
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..8390ff8f3e710164895752b1c77c9a45f2c3a2cd
GIT binary patch
literal 2024
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
GLSTaOh{MeQ
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e0352ac69fc610dd8fe36777642eb5fcdbbe16e5
GIT binary patch
literal 3168
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
GLSTY7Pt<Mz
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..396b967c651549a73abc96fd1ca1b0fdfc298d9f
GIT binary patch
literal 865
zc$@)Y1D^beP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10^UhPK~y-6rISx+6L}QJzi%>`nHWu>riW77lPd};1rK5?qR?JM
zK?<R??5(h@p&){-FtE@iIOsu9{BuzXVv9oYA6?oUwghz##iTWO5T#mj2(%U)#?EeL
zewp7N58G<}bMwKwynlS&<NHV{g`Y9~+kkD`otmZ{wk)e#O1YhgR*C3!7=|yTlz%yn
z^I~H?trOU`z1uX+n=LIZdpkNh%*MvXSTdOe=Nv&0!1sNX&*wwW^NNhIGmhgFKM$~N
zyQ`t0;c;JIU!u9WnS>DEp%M{@2+PaMYJ7aW=KKCp$8ny<>h=0Bi9}*1o6T-M_Q&7!
zYu9#2DZg^}RT4(yS==b>z@c5k)ZX51l*{EzKA(TA8HQo^_V#W$ef=NssE)zQ1@M&>
zh=&g$78W3cK=3ahgg_W@__Y<hTDpr&CX+M_BdZZncRHP>izhq4qX?I>orvz=2g~OX
zo;vk^<JtgL<9PJ^cf9}b7U^`Fi0HV+Ip3=5I)o4qQ3N3bR96?oks}B_?;C;$tG<ha
zJI^8(i$O}+py|5)#&ul?A;6<LIOq5{Fo2+3Mo_7MbB>4soO7^%<KwD}m+v0H^E_yp
z_C#ZhjZ9BZ*DTAzj%_J){&ojq&mPofXCV(9Kv*gPSR<%$oY;R23)QEXnwkn2W22;$
z!pO+T)uyJVfu5e8gi;Do6#Y<j@pv4DVPJA{GFV((ypYS~{@f^}?sz<YFO$inQmK?4
zMG-8^f@zviN+AqGR4SGF%*;$(2ys4_%boj5i2yJ>JiM>9we?wle}6(siTU|?RxX#}
z`@Yh3y~-H7SFKjBjE#-CN-6pI7l?=e=tDz8-ED1cy6d`_n3(v_^Ss-oQt3vaP*?#V
z0SEvbfJsCg0AGJ50zj5!W#;DQYQ<vl+0xR|t<llZ*|nSoK&{tpIE#W(Uwm+IFaaP2
rAP&F+un9mCKmvfdMy#!`u9U(j@I#i1ARQ3<00000NkvXXu0mjfd3=s2
new file mode 100644
--- /dev/null
+++ b/chat/protocols/gtalk/jar.mn
@@ -0,0 +1,5 @@
+chat.jar:
+% skin prpl-gtalk classic/1.0 %skin/classic/prpl/gtalk/
+	skin/classic/prpl/gtalk/icon32.png	(icons/prpl-gtalk-32.png)
+	skin/classic/prpl/gtalk/icon48.png	(icons/prpl-gtalk-48.png)
+	skin/classic/prpl/gtalk/icon.png	(icons/prpl-gtalk.png)
new file mode 100644
--- /dev/null
+++ b/chat/protocols/irc/Makefile.in
@@ -0,0 +1,64 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is Instantbird.
+#
+# The Initial Developer of the Original Code is
+# Patrick Cloke <clokep@gmail.com>.
+# Portions created by the Initial Developer are Copyright (C) 2011
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH		= ../../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+EXTRA_COMPONENTS = \
+		irc.js \
+		irc.manifest \
+		$(NULL)
+
+EXTRA_JS_MODULES = \
+		ircBase.jsm \
+		ircCTCP.jsm \
+		ircDCC.jsm \
+		ircCommands.jsm \
+		ircHandlers.jsm \
+		ircISUPPORT.jsm \
+		ircUtils.jsm \
+		$(NULL)
+
+ifdef ENABLE_TESTS
+relativesrcdir = chat/protocols/irc
+XPCSHELL_TESTS = test
+endif
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..003103914c44cf260b957fc4f8928264212ca418
GIT binary patch
literal 695
zc$@*Z0!aOdP)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F800004b3#c}2nYxW
zd<bNS00009a7bBm000R(000R(0q|s!N&o-=8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10yIfPK~z|U?Uyl&8c`6(|MNVv;$}s1+y@8;se)C4l3;Cd3FJGZ
zUyp!IU=_5oyG>_pC29NuqD`(60vZWiilCqbLM~yAup0MGimaDaP7n7mo5F!D-rF}b
zznS^N;}J6>NE3l(-vj&)00-MT7>02_7K@z$IP#J2t7TcIrBdm$Zvaix-srmiOo;89
zC#8gGngYOc-vC5(D1^XdGT9nHRaFqtp?5EE4FnFbGy=mgjx|j)iRj)2pNF+t4N}Ub
zO+`_VOePVJ$NdAuVzDz_*H8QktX{7plgao80B|dTO{deX{Q}p(9$*g;IKb9-!6p+z
zAR3KctsTd~Y&JtA5&;oGN(pBEzB7Q)X!O%G&5v$!kxHeG)9G})*=$<9Uhl$joXhce
ze7Z9LBI@RHxhIQtKA(R&91h>ww(VrI*~i!U>{J8AV)0$4(|I%)4BoFhvvU!a%jJ;}
z;>EiEt0VX(rG%=gkW#|7Z77O@P$-0(-VY0b5z!s*HZ#u)g@Rn?w*+8W)+^IA52Tbw
ziA3T-CX>+`jmD35yL};qU}h%wD6M|XeA(@G3jpqVLe7au0$>0RfH^bIy*bHS84(eH
zFo1BSQu(AP3N;#ym(^<Z!^ZO0+tdYg0L%f*0Q_R+*|i<;R3!TSe*66V{B^6<`U3x2
zaR5Z{rvv21AtEAnE2q`j5>dzn3%d@E8^Dh(Ix6wE3v*_6t`2Mxmpc;hEncuU#n=e&
dFD&rO=r;q`=+rK*U1b0O002ovPDHLkV1f@IEHwZC
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..606425fabb7aa1d5e9e490ad5d74bc55c4c94262
GIT binary patch
literal 1003
zc$@+20~Gv;P)<h;3K|Lk000e1NJLTq001xm001xu1^@s6R|5Hm00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H1187M^K~!jg?U=t$BS#d+zi+%+yV$I+Kr(0}Aw^M&6Dgw9`~gWS
znp2ukAkt7pbb!S+wu=ZX3W}Uk5k>M<oP?AJQV=0gQKW*TqJR@Q5nG#h5zb^iKT=>5
zo9u$QTeMi(k2LaXy!)N^-Fa_Dv!u1gFvF4#;B83_Bu#1{X;K48W5g-8w6ye@VHn>k
zrN-jaWsJQRLVQ^+mw&kVU#tcU!+1VFKR;&McAT>CeSfT4tv(0v?*GP&b0DRZW7{^G
z&1Rgk$mjD=N;zTw4Sfxy29hQ<kTjvI(89vP$JuOlg@``5k60#?LA6>%)EOh6&tr0O
z5~HJ|ZxCuYp$4+q?02)Xvrh|!LTm#8!1?((Ha9mhJv|+pW~c!v<x|ITaB*=F+du%|
zI1cLddaRnaZe&WSSO<FuhQ5eX14)w_NSf3@(hR+USWjew5_RMZf&iRz<Z?Nfrg`1p
zYPG-^I}dv%5d)P<rQNwC0MJ^)vMf0>Gn2I}3q*vqwKdUhw-o?z&Rar=ufntvNg*la
z6Dj2r&+}~0^K7NmJB>!;lhxJL=IQCFmQup^{ogs~1<rZl=;&yqTrR%|(@JCpT-R-d
zbpKo|7C+qB*!XE;V&Z)wQm*U%b(hwoD&kV9^olX|(c$6YOCiKRgFKVS4BUq6x*Wi>
z#l^)>2f6=I%|I`eO6A8v?t8Qb1|>9w7-PN=!nQ2yx-SR<1VI4PH2c=*`yhk>5&1+E
zmAXo6eXmngsE|^sw6d~N8Xq4I0Kha&V`^$DXBY;w*4WzG;>~7L5)lBP_qTi9g^0iy
z(*SUCa$<7MzXp(th({8U2GHA-)>=jxpr|hhb8~Z!VHocTA=t>s$ctjJIC^<`iS_mM
zzw7mS(eu1twAKUwjIr*9L}7j1>$9`7SG&8re?$cXppC1|O#@IpO#+Z%MnY$Rh%x}`
z0R9A!SzcZa4h|0F&dyHr`1ttqTCMimEkWEx)Z0fV_{@#KMW-J`gqtR!a*=u(lTwQ9
z?d@Op_V&Km-`{@?;LVEQZm6RzI!)thp;0~Y^buvCF9&_!|E5-}ecxy_x^Lv6K36CJ
z0s7NN-H4};$QtORQMa+47Fkd+Vc;G$5NY80hASfs|4m_6{2}PRq_<y%hJ~TuiBkhf
Z^Dp>tY{BBgkX!%&002ovPDHLkV1o1+&{Y5c
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..19d578deda123c41b441aef78f147fdf74bd426d
GIT binary patch
literal 454
zc$@*o0XhDOP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10YgbdK~y-6rIRsk!Y~wuf8$utFeE()ONShwW2UYgp<Bm90fy94
zrb<0UyHzY%A_fkS3-lDFOM;s`UI#!GkN{DC$+Dg-eeeChZNbbq)@(L=tEy^za&RQ2
z)H|5@WCcOMbzSp0)pgB55U|xomr`Q2TD5j}T^9h*yENt{{TpDl1r~<kK|LCc;QRh5
zpsK2|)*4dE)-FVZ$z;+D0Im;gI2=Ms2_XcC=nrtM@;o=5=k>MLv|g`;Qfl4_kf!O&
zVzGEC%kl=mZ5+qnoo+#E9Ti1!2Ve=n039s=K$0Y%0Pgp-1#DAx90$w{B7$Wda`%BX
z$x=#vOsCU_!C=5q6v;f#O_pVSBI3<v!~6fCl$tjI1T!NHLkGZ@ZQG?W=9P$^0sH_E
w*uK_Yk|d^ag57QaEQ_Le+<823a3&J`0+wL8z&BAwA^-pY07*qoM6N<$f>sE!>;M1&
new file mode 100644
--- /dev/null
+++ b/chat/protocols/irc/irc.js
@@ -0,0 +1,916 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Instantbird.
+ *
+ * The Initial Developer of the Original Code is
+ * Patrick Cloke <clokep@gmail.com>.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource:///modules/imXPCOMUtils.jsm");
+Cu.import("resource:///modules/imServices.jsm");
+Cu.import("resource:///modules/ircUtils.jsm");
+Cu.import("resource:///modules/ircHandlers.jsm");
+Cu.import("resource:///modules/jsProtoHelper.jsm");
+Cu.import("resource:///modules/socket.jsm");
+
+// Parses a raw IRC message into an object (see section 2.3 of RFC 2812).
+function ircMessage(aData) {
+  LOG(aData);
+  let message = {rawMessage: aData};
+  let temp;
+
+  // Splits the raw string into four parts (the second is required), the command
+  // is required. A raw string looks like:
+  //   [":" <source> " "] <command> [" " <parameter>]* [":" <last parameter>]
+  //     <source>: :(<server url> | <nickname> [["!" <user>] "@" <host>])
+  //     <command>: /[^ ]+/
+  //     <parameter>: /[^ ]+/
+  //     <last parameter>: /.+/
+  // See http://joshualuckers.nl/2010/01/10/regular-expression-to-match-raw-irc-messages/
+  if ((temp = aData.match(/^(?::([^ ]+) )?([^ ]+)(?: ((?:[^: ][^ ]* ?)*))?(?: ?:(.*))?$/))) {
+    // Assume message is from the server if not specified
+    message.source = temp[1] || this._server;
+    message.command = temp[2];
+    // Space separated parameters
+    message.params = temp[3] ? temp[3].trim().split(/ +/) : [];
+    if (temp[4]) // Last parameter can contain spaces
+      message.params.push(temp[4]);
+
+    // The source string can be split into multiple parts as:
+    //   :(server|nickname[[!user]@host]
+    // If the source contains a . or a :, assume it's a server name. See RFC
+    // 2812 Section 2.3 definition of servername vs. nickname.
+    if (message.source &&
+        (temp = message.source.match(/^([^ !@\.:]+)(?:!([^ @]+))?(?:@([^ ]+))?$/))) {
+      message.nickname = temp[1];
+      message.user = temp[2] || null; // Optional
+      message.host = temp[3] || null; // Optional
+    }
+  }
+
+  return message;
+}
+
+function ircChannel(aAccount, aName, aNick) {
+  this._init(aAccount, aName, aNick);
+}
+ircChannel.prototype = {
+  __proto__: GenericConvChatPrototype,
+  sendMsg: function(aMessage) {
+    this._account.sendMessage("PRIVMSG", [this.name, aMessage]);
+
+    // Since we don't receive a message back from the server, just assume it
+    // was received and write it. An IRC bouncer will send us our message back
+    // though, try to handle that.
+    if (this.hasParticipant(this._account._nickname))
+      this.writeMessage(this.nick, aMessage, {outgoing: true});
+  },
+  // Overwrite the writeMessage function to apply CTCP formatting before
+  // display.
+  writeMessage: function(aWho, aText, aProperties) {
+    GenericConvChatPrototype.writeMessage.call(this, aWho,
+                                               ctcpFormatToHTML(aText),
+                                               aProperties);
+  },
+
+  // Section 3.2.2 of RFC 2812.
+  part: function(aMessage) {
+    let params = [this.name];
+
+    // If a valid message was given, use it as the part message.
+    // Otherwise, fall back to the default part message, if it exists.
+    let msg = aMessage || this._account.getString("partmsg");
+    if (msg)
+      params.push(msg);
+
+    this._account.sendMessage("PART", params);
+  },
+
+  close: function() {
+    // Part the room if we're connected.
+    if (this._account.connected && !this.left)
+      this.part();
+    GenericConvChatPrototype.close.call(this);
+  },
+
+  unInit: function() {
+    this._account.removeConversation(this.name);
+    GenericConvChatPrototype.unInit.call(this);
+  },
+
+  getNormalizedChatBuddyName: function(aNick)
+    this._account.normalize(aNick, this._account.userPrefixes),
+
+  hasParticipant: function(aNick)
+    hasOwnProperty(this._participants, this.getNormalizedChatBuddyName(aNick)),
+
+  getParticipant: function(aNick, aNotifyObservers) {
+    let normalizedNick = this.getNormalizedChatBuddyName(aNick);
+    if (this.hasParticipant(aNick))
+      return this._participants[normalizedNick];
+
+    let participant = new ircParticipant(aNick, this._account);
+    this._participants[normalizedNick] = participant;
+    if (aNotifyObservers) {
+      this.notifyObservers(new nsSimpleEnumerator([participant]),
+                           "chat-buddy-add");
+    }
+    return participant;
+  },
+  updateNick: function(aOldNick, aNewNick) {
+    if (!this.hasParticipant(aOldNick)) {
+      ERROR("Trying to rename nick that doesn't exist! " + aOldNick + " to " +
+            aNewNick);
+      return;
+    }
+
+    // Get the original ircParticipant and then remove it.
+    let participant = this.getParticipant(aOldNick);
+    this.removeParticipant(aOldNick);
+
+    // Update the nickname and add it under the new nick.
+    participant._name = aNewNick;
+    this._participants[this.getNormalizedChatBuddyName(aNewNick)] = participant;
+
+    this.notifyObservers(participant, "chat-buddy-update", aOldNick);
+  },
+  removeParticipant: function(aNick, aNotifyObservers) {
+    if (!this.hasParticipant(aNick))
+      return;
+
+    if (aNotifyObservers) {
+      let stringNickname = Cc["@mozilla.org/supports-string;1"]
+                              .createInstance(Ci.nsISupportsString);
+      stringNickname.data = aNick;
+      this.notifyObservers(new nsSimpleEnumerator([stringNickname]),
+                           "chat-buddy-remove");
+    }
+    delete this._participants[this.getNormalizedChatBuddyName(aNick)];
+  },
+  // Use this before joining to avoid errors of trying to re-add an existing
+  // participant
+  removeAllParticipants: function() {
+    let stringNicknames = [];
+    for (let nickname in this._participants) {
+      let stringNickname = Cc["@mozilla.org/supports-string;1"]
+                              .createInstance(Ci.nsISupportsString);
+      stringNickname.data = this._participants[nickname].name;
+      stringNicknames.push(stringNickname);
+    }
+    this.notifyObservers(new nsSimpleEnumerator(stringNicknames),
+                         "chat-buddy-remove");
+    this._participants = {};
+  },
+
+  _left: false,
+  get left() this._left,
+  set left(aLeft) {
+    this._left = aLeft;
+
+    // If we've left, notify observers.
+    if (this._left)
+      this.notifyObservers(null, "update-conv-chatleft");
+  },
+  get topic() this._topic, // can't add a setter without redefining the getter
+  set topic(aTopic) {
+    this._account.sendMessage("TOPIC", [this.name, aTopic]);
+  },
+  get topicSettable() true,
+
+  get normalizedName() this._account.normalize(this.name),
+};
+
+function ircParticipant(aName, aAccount) {
+  this._name = aName;
+  this._account = aAccount;
+  this._modes = [];
+
+  if (this._name[0] in this._account.userPrefixToModeMap) {
+    this._modes.push(this._account.userPrefixToModeMap[this._name[0]]);
+    this._name = this._name.slice(1);
+  }
+}
+ircParticipant.prototype = {
+  __proto__: GenericConvChatBuddyPrototype,
+
+  // This takes a mode change string and reflects it into the
+  // prplIConvChatBuddy, a mode change string is of the form:
+  //   ("+" | "-")<mode key>[<mode key>]*
+  // e.g. +iaw or -i
+  setMode: function(aNewMode) {
+    // Are we going to add or remove the modes?
+    if (aNewMode[0] != "+" && aNewMode[0] != "-") {
+      WARN("Invalid mode string: " + aNewMode);
+      return;
+    }
+    let addNewMode = aNewMode[0] == "+";
+
+    // Check each mode being added and update the user
+    for (let i = 1; i < aNewMode.length; i++) {
+      let index = this._modes.indexOf(aNewMode[i]);
+      // If the mode is in the list of modes and we want to remove it.
+      if (index != -1 && !addNewMode)
+        this._modes.splice(index, 1);
+      // If the mode is not in the list of modes and we want to add it.
+      else if (index == -1 && addNewMode)
+        this._modes.push(aNewMode[i]);
+    }
+  },
+
+  get voiced() this._modes.indexOf("v") != -1,
+  get halfOp() this._modes.indexOf("h") != -1,
+  get op() this._modes.indexOf("o") != -1,
+  get founder() this._modes.indexOf("n") != -1,
+  get typing() false
+};
+
+function ircConversation(aAccount, aName) {
+  this._init(aAccount, aName);
+}
+ircConversation.prototype = {
+  __proto__: GenericConvIMPrototype,
+  sendMsg: function(aMessage) {
+    this._account.sendMessage("PRIVMSG", [this.name, aMessage]);
+
+    // Since the server doesn't send us a message back, just assume the message
+    // was received and immediately show it.
+    this.writeMessage(this._account._nickname, aMessage, {outgoing: true});
+  },
+
+  // Overwrite the writeMessage function to apply CTCP formatting before
+  // display.
+  writeMessage: function(aWho, aText, aProperties) {
+    GenericConvIMPrototype.writeMessage.call(this, aWho,
+                                             ctcpFormatToHTML(aText),
+                                             aProperties);
+  },
+
+  get normalizedName() this._account.normalize(this.name),
+
+  unInit: function() {
+    this._account.removeConversation(this.name);
+    GenericConvIMPrototype.unInit.call(this);
+  },
+
+  updateNick: function(aNewNick) {
+    this._name = aNewNick;
+    this.notifyObservers(null, "update-conv-title");
+  }
+};
+
+function ircSocket(aAccount) {
+  this._account = aAccount;
+  this._initCharsetConverter();
+}
+ircSocket.prototype = {
+  __proto__: Socket,
+  delimiter: "\r\n",
+  connectTimeout: 60, // Failure to connect after 1 minute
+  readWriteTimeout: 300, // Failure when no data for 5 minutes
+  _converter: null,
+
+  _initCharsetConverter: function() {
+    this._converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+                        .createInstance(Ci.nsIScriptableUnicodeConverter);
+    try {
+      this._converter.charset = this._account._encoding;
+    } catch (e) {
+      delete this._converter;
+      ERROR("Failed to set character set to: " + this._account._encoding + " for " +
+            this._account.name + ".");
+    }
+  },
+
+  // Implement Section 5 of RFC 2812.
+  onDataReceived: function(aRawMessage) {
+    DEBUG(aRawMessage);
+    if (this._converter) {
+      try {
+        aRawMessage = this._converter.ConvertToUnicode(aRawMessage);
+      } catch (e) {
+        WARN("This message doesn't seem to be " + this._account._encoding +
+             " encoded: " + aRawMessage);
+        // Unfortunately, if the unicode converter failed once,
+        // it will keep failing so we need to reinitialize it.
+        this._initCharsetConverter();
+      }
+    }
+
+    // If nothing handled the message, throw an error.
+    if (!ircHandlers.handleMessage(this._account, new ircMessage(aRawMessage)))
+      WARN("Unhandled IRC message: " + aRawMessage);
+  },
+  onConnection: function() {
+    this._account._connectionRegistration.call(this._account);
+  },
+
+  // Throw errors if the socket has issues.
+  onConnectionClosed: function () {
+    if (!this._account.imAccount || this._account.disconnecting ||
+        this._account.disconnected)
+      return;
+
+    ERROR("Connection closed by server.");
+    this._account.gotDisconnected(Ci.prplIAccount.ERROR_NETWORK_ERROR,
+                                  _("connection.error.lost"));
+  },
+  onConnectionReset: function () {
+    ERROR("Connection reset.");
+    this._account.gotDisconnected(Ci.prplIAccount.ERROR_NETWORK_ERROR,
+                                  _("connection.error.lost"));
+  },
+  onConnectionTimedOut: function() {
+    ERROR("Connection timed out.");
+    this._account.gotDisconnected(Ci.prplIAccount.ERROR_NETWORK_ERROR,
+                                  _("connection.error.timeOut"));
+  },
+  onCertificationError: function(aSocketInfo, aStatus, aTargetSite) {
+    ERROR("Certification error.");
+    this._account.gotDisconnected(Ci.prplIAccount.ERROR_CERT_OTHER_ERROR,
+                                  _("connection.error.certError"));
+  },
+  log: LOG
+};
+
+function ircAccountBuddy(aAccount, aBuddy, aTag, aUserName) {
+  this._init(aAccount, aBuddy, aTag, aUserName);
+}
+ircAccountBuddy.prototype = {
+  __proto__: GenericAccountBuddyPrototype,
+
+  // Returns a list of imITooltipInfo objects to be displayed when the user
+  // hovers over the buddy.
+  getTooltipInfo: function() this._account.getBuddyInfo(this.normalizedName),
+
+  get normalizedName() this._account.normalize(this.userName),
+
+  // Can not send messages to buddies who appear offline.
+  get canSendMessage() this.account.connected,
+
+  // Called when the user wants to chat with the buddy.
+  createConversation: function() this._account.createConversation(this.userName)
+};
+
+function ircAccount(aProtocol, aImAccount) {
+  this._buddies = {};
+  this._init(aProtocol, aImAccount);
+  this._conversations = {};
+
+  // Split the account name into usable parts.
+  let splitter = aImAccount.name.lastIndexOf("@");
+  this._nickname = aImAccount.name.slice(0, splitter);
+  this._server = aImAccount.name.slice(splitter + 1);
+
+  // For more information, see where these are defined in the prototype below.
+  this._isOnQueue = [];
+  this.pendingIsOnQueue = [];
+  this.whoisInformation = {};
+}
+ircAccount.prototype = {
+  __proto__: GenericAccountPrototype,
+  _socket: null,
+  _MODE_WALLOPS: 1 << 2, // mode 'w'
+  _MODE_INVISIBLE: 1 << 3, // mode 'i'
+  get _mode() 0,
+
+  get noNewlines() true,
+
+  get normalizedName() this.normalize(this.name),
+
+  // Parts of the specification give max lengths, keep track of them since a
+  // server can overwrite them. The defaults given here are from RFC 2812.
+  maxNicknameLength: 9, // 1.2.1 Users
+  maxChannelLength: 50, // 1.3 Channels
+  maxMessageLength: 512, // 2.3 Messages
+  maxHostnameLength: 63, // 2.3.1 Message format in Augmented BNF
+
+  // The default prefixes.
+  userPrefixes: ["@", "!", "%", "+"],
+  // The default prefixes to modes.
+  userPrefixToModeMap: {"@": "o", "!": "n", "%": "h", "+": "v"},
+  channelPrefixes: ["&", "#", "+", "!"], // 1.3 Channels
+
+  // Handle Scandanavian lower case (optionally remove status indicators).
+  // See Section 2.2 of RFC 2812: the characters {}|^ are considered to be the
+  // lower case equivalents of the characters []\~, respectively.
+  normalize: function(aStr, aPrefixes) {
+    let str = aStr;
+    if (aPrefixes && aPrefixes.indexOf(aStr[0]) != -1)
+      str = str.slice(1);
+
+    return str.replace(/[\x41-\x5E]/g,
+                       function(c) String.fromCharCode(c.charCodeAt(0) + 0x20));
+  },
+
+  isMUCName: function(aStr) {
+    return (this.channelPrefixes.indexOf(aStr[0]) != -1);
+  },
+
+  // Tell the server about status changes. IRC is only away or not away;
+  // consider the away, idle and unavailable status type to be away.
+  isAway: false,
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic != "status-changed")
+      return;
+
+    let {statusType: type, statusText: text} = this.imAccount.statusInfo;
+    LOG("New status: " + type + ", " + text);
+
+    // Tell the server to mark us as away.
+    if (type < Ci.imIStatusInfo.STATUS_AVAILABLE && !this.isAway) {
+      // We have to have a string in order to set IRC as AWAY.
+      if (!text) {
+        // If no status is given, use the the default idle/away message.
+        const IDLE_PREF_BRANCH = "messenger.status.";
+        const IDLE_PREF = "defaultIdleAwayMessage";
+        text = Services.prefs.getComplexValue(IDLE_PREF_BRANCH + IDLE_PREF,
+                                              Ci.nsIPrefLocalizedString).data;
+
+        if (!text) {
+          // Get the default value of the localized preference.
+          text = Services.prefs.getDefaultBranch(IDLE_PREF_BRANCH)
+                         .getComplexValue(IDLE_PREF,
+                                          Ci.nsIPrefLocalizedString).data;
+        }
+        // The last resort, fallback to a non-localized string.
+        if (!text)
+          text = "Away";
+      }
+      this.sendMessage("AWAY", text); // Mark as away.
+    }
+    else if (type == Ci.imIStatusInfo.STATUS_AVAILABLE && this.isAway)
+      this.sendMessage("AWAY"); // Mark as back.
+  },
+
+  // The whois information: nicks are used as keys and refer to a map of field
+  // to value.
+  whoisInformation: {},
+  // Request WHOIS information on a buddy when the user requests more
+  // information.
+  requestBuddyInfo: function(aBuddyName) {
+    this.sendMessage("WHOIS", aBuddyName);
+  },
+  // Return an nsISimpleEnumerator of imITooltipInfo for a given nick.
+  getBuddyInfo: function(aNick) {
+    let nick = this.normalize(aNick);
+    if (!hasOwnProperty(this.whoisInformation, nick))
+      return EmptyEnumerator;
+
+    let whoisInformation = this.whoisInformation[nick];
+    let tooltipInfo = [];
+    for (let field in whoisInformation) {
+      let value = whoisInformation[field];
+      tooltipInfo.push(new TooltipInfo(_("tooltip." + field), value));
+    }
+
+    return new nsSimpleEnumerator(tooltipInfo);
+  },
+
+  addBuddy: function(aTag, aName) {
+    let buddy = new ircAccountBuddy(this, null, aTag, aName);
+    this._buddies[buddy.normalizedName] = buddy;
+
+    // Put the username as the first to be checked on the next ISON call.
+    this._isOnQueue.unshift(buddy.userName);
+
+    Services.contacts.accountBuddyAdded(buddy);
+  },
+  // Loads a buddy from the local storage. Called for each buddy locally stored
+  // before connecting to the server.
+  loadBuddy: function(aBuddy, aTag) {
+    let buddy = new ircAccountBuddy(this, aBuddy, aTag);
+    this._buddies[buddy.normalizedName] = buddy;
+    // Put each buddy name into the ISON queue.
+    this._isOnQueue.push(buddy.userName);
+    return buddy;
+  },
+  hasBuddy: function(aName)
+    hasOwnProperty(this._buddies, this.normalize(aName, this.userPrefixes)),
+  // Return an array of buddy names.
+  getBuddyNames: function() {
+    let buddies = [];
+    for each (let buddyName in Object.keys(this._buddies))
+      buddies.push(this._buddies[buddyName].userName);
+    return buddies;
+  },
+  getBuddy: function(aName) {
+    if (this.hasBuddy(aName))
+      return this._buddies[this.normalize(aName, this.userPrefixes)];
+    return null;
+  },
+  changeBuddyNick: function(aOldNick, aNewNick) {
+    let msg;
+    // Your nickname changed!
+    if (this.normalize(aOldNick) == this.normalize(this._nickname)) {
+      this._nickname = aNewNick;
+      msg = _("message.nick.you", aNewNick);
+    }
+    else
+      msg = _("message.nick", aOldNick, aNewNick);
+
+    for each (let conversation in this._conversations) {
+      if (conversation.isChat && conversation.hasParticipant(aOldNick)) {
+        // Update the nick in every chat conversation the user is in.
+        conversation.updateNick(aOldNick, aNewNick);
+        conversation.writeMessage(aOldNick, msg, {system: true});
+      }
+    }
+
+    // If a private conversation is open with that user, change its title.
+    if (this.hasConversation(aOldNick)) {
+      // Get the current conversation and rename it.
+      let conversation = this.getConversation(aOldNick);
+
+      // Remove the old reference to the conversation and create a new one.
+      this.removeConversation(aOldNick);
+      this._conversations[this.normalize(aNewNick)] = conversation;
+
+      conversation.updateNick(aNewNick);
+      conversation.writeMessage(aOldNick, msg, {system: true});
+    }
+  },
+
+  countBytes: function(aStr) {
+    // Assume that if it's not UTF-8 then each character is 1 byte.
+    if (this._encoding != "UTF-8")
+      return aStr.length;
+
+    // Count the number of bytes in a UTF-8 encoded string.
+    function charCodeToByteCount(c) {
+      // Unicode characters with a code point >  127 are 2 bytes long.
+      // Unicode characters with a code point > 2047 are 3 bytes long.
+      return c < 128 ? 1 : c < 2048 ? 2 : 3;
+    }
+    let bytes = 0;
+    for (let i = 0; i < aStr.length; i++)
+      bytes += charCodeToByteCount(aStr.charCodeAt(i));
+    return bytes;
+  },
+
+  // To check if users are online, we need to queue multiple messages.
+  // An internal queue of all nicks that we wish to know the status of.
+  _isOnQueue: [],
+  // The nicks that were last sent to the server that we're waiting for a
+  // response about.
+  pendingIsOnQueue: [],
+  // The time between sending isOn messages (milliseconds).
+  _isOnDelay: 60 * 1000,
+  _isOnTimer: null,
+  // The number of characters that are available to be filled with nicks for
+  // each ISON message.
+  _isOnLength: null,
+  // Generate and send an ISON message to poll for each nick's status.
+  sendIsOn: function() {
+    // Add any previously pending queue to the end of the ISON queue.
+    if (this.pendingIsOnQueue)
+      this._isOnQueue = this._isOnQueue.concat(this.pendingIsOnQueue);
+
+    // If no buddies, just look again after the timeout.
+    if (this._isOnQueue.length) {
+      // Calculate the possible length of names we can send.
+      if (!this._isOnLength) {
+        let length = this.countBytes(this.buildMessage("ISON", " ")) + 2;
+        this._isOnLength = this.maxMessageLength - length + 1;
+      }
+
+      // Always add the next nickname to the pending queue, this handles a silly
+      // case where the next nick is greater than or equal to the maximum
+      // message length.
+      this.pendingIsOnQueue = [this._isOnQueue.shift()];
+
+      // Attempt to maximize the characters used in each message, this may mean
+      // that a specific user gets sent very often since they have a short name!
+      let buddiesLength = this.countBytes(this.pendingIsOnQueue[0]);
+      for (let i = 0; i < this._isOnQueue.length; i++) {
+        // If we can fit the nick, add it to the current buffer.
+        if ((buddiesLength + this.countBytes(this._isOnQueue[i])) < this._isOnLength) {
+          // Remove the name from the list and add it to the pending queue.
+          let nick = this._isOnQueue.splice(i--, 1)[0];
+          this.pendingIsOnQueue.push(nick);
+
+          // Keep track of the length of the string, the + 1 is for the spaces.
+          buddiesLength += this.countBytes(nick) + 1;
+
+          // If we've filled up the message, stop looking for more nicks.
+          if (buddiesLength >= this._isOnLength)
+            break;
+        }
+      }
+
+      // Send the message.
+      this.sendMessage("ISON", this.pendingIsOnQueue.join(" "));
+    }
+
+    // Call this function again in _isOnDelay seconds.
+    // This makes the assumption that this._isOnDelay >> the response to ISON
+    // from the server.
+    this._isOnTimer = setTimeout(this.sendIsOn.bind(this), this._isOnDelay);
+  },
+
+  connect: function() {
+    this.reportConnecting();
+
+    // Load preferences.
+    this._port = this.getInt("port");
+    this._ssl = this.getBool("ssl");
+    // Use the display name as the user's real name.
+    this._realname = this.imAccount.statusInfo.displayName;
+    this._encoding = this.getString("encoding") || "UTF-8";
+    this._showServerTab = this.getBool("showServerTab");
+
+    // Open the socket connection.
+    this._socket = new ircSocket(this);
+    this._socket.connect(this._server, this._port, this._ssl ? ["ssl"] : []);
+  },
+
+  // Used to wait for a response from the server.
+  _quitTimer: null,
+  // RFC 2812 Section 3.1.7.
+  quit: function(aMessage) {
+    this.sendMessage("QUIT",
+                     aMessage || this.getString("quitmsg") || undefined);
+  },
+  // When the user clicks "Disconnect" in account manager
+  disconnect: function() {
+    if (this.disconnected || this.disconnecting)
+       return;
+
+    this.reportDisconnecting(Ci.prplIAccount.NO_ERROR);
+
+    // Let the server know we're going to disconnect.
+    this.quit();
+
+    // Give the server 2 seconds to respond, otherwise just forcefully
+    // disconnect the socket. This will be cancelled if a response is heard from
+    // the server.
+    this._quitTimer = setTimeout(this.gotDisconnected.bind(this), 2 * 1000);
+  },
+
+  createConversation: function(aName) this.getConversation(aName),
+
+  // aComponents implements prplIChatRoomFieldValues.
+  joinChat: function(aComponents) {
+    let params = [aComponents.getValue("channel")];
+    let password = aComponents.getValue("password");
+    if (password)
+      params.push(password);
+    this.sendMessage("JOIN", params);
+  },
+
+  chatRoomFields: {
+    "channel": {"label": _("joinChat.channel"), "required": true},
+    "password": {"label": _("joinChat.password"), "isPassword": true}
+  },
+
+  parseDefaultChatName: function(aDefaultName) ({"channel": aDefaultName}),
+
+  // Attributes
+  get canJoinChat() true,
+
+  hasConversation: function(aConversationName)
+    hasOwnProperty(this._conversations, this.normalize(aConversationName)),
+
+  // Returns a conversation (creates it if it doesn't exist)
+  getConversation: function(aName) {
+    let name = this.normalize(aName);
+    if (!this.hasConversation(aName)) {
+      let constructor = this.isMUCName(aName) ? ircChannel : ircConversation;
+      this._conversations[name] = new constructor(this, aName, this._nickname);
+    }
+    return this._conversations[name];
+  },
+
+  removeConversation: function(aConversationName) {
+    if (this.hasConversation(aConversationName))
+      delete this._conversations[this.normalize(aConversationName)];
+  },
+
+  // This builds the message string that will be sent to the server.
+  buildMessage: function(aCommand, aParams) {
+    if (!aCommand) {
+      ERROR("IRC messages must have a command.");
+      return null;
+    }
+
+    // Ensure a command is only characters or numbers.
+    if (!/^[A-Z0-9]+$/i.test(aCommand)) {
+      ERROR("IRC command invalid: " + aCommand);
+      return null;
+    }
+
+    let message = aCommand;
+    // If aParams is empty, then use an empty array. If aParams is not an array,
+    // consider it to be a single parameter and put it into an array.
+    let params = !aParams ? [] : Array.isArray(aParams) ? aParams : [aParams];
+    if (params.length) {
+      if (params.slice(0, -1).some(function(p) p.indexOf(" ") != -1)) {
+        ERROR("IRC parameters cannot have spaces: " + params.slice(0, -1));
+        return null;
+      }
+      // Join the parameters with spaces, except the last parameter which gets
+      // joined with a " :" before it (and can contain spaces).
+      message += " " + params.concat(":" + params.pop()).join(" ");
+    }
+
+    return message;
+  },
+
+  // Shortcut method to build & send a message at once. Use aLoggedData to log
+  // something different than what is actually sent.
+  sendMessage: function(aCommand, aParams, aLoggedData) {
+    this.sendRawMessage(this.buildMessage(aCommand, aParams), aLoggedData);
+  },
+
+  // This sends a message over the socket and catches any errors. Use
+  // aLoggedData to log something different than what is actually sent.
+  sendRawMessage: function(aMessage, aLoggedData) {
+    // TODO This should escape any characters that can't be used in IRC (e.g.
+    // \001, \r\n).
+
+    let length = this.countBytes(aMessage) + 2;
+    if (length > this.maxMessageLength) {
+      // Log if the message is too long, but try to send it anyway.
+      WARN("Message length too long (" + length + " > " +
+           this.maxMessageLength + "\n" + aMessage);
+    }
+
+    try {
+      this._socket.sendString(aMessage, this._encoding, aLoggedData);
+    } catch (e) {
+      try {
+        WARN("Failed to convert " + aMessage + " from Unicode to " +
+             this._encoding + ".");
+        this._socket.sendData(aMessage, aLoggedData);
+      } catch(e) {
+        ERROR("Socket error: " + e);
+        this.gotDisconnected(Ci.prplIAccount.ERROR_NETWORK_ERROR,
+                             _("connection.error.lost"));
+      }
+    }
+  },
+
+  // CTCP messages are \001<COMMAND> [<parameters>]*\001.
+  sendCTCPMessage: function(aCommand, aParams, aTarget, aIsNotice) {
+    // Combine the CTCP command and parameters into the single IRC param.
+    let ircParam = "\x01" + aCommand;
+    // If aParams is empty, then use an empty array. If aParams is not an array,
+    // consider it to be a single parameter and put it into an array.
+    let params = !aParams ? [] : Array.isArray(aParams) ? aParams : [aParams];
+    if (params.length)
+      ircParam += " " + params.join(" ");
+    ircParam += "\x01";
+
+    // Send the IRC message as a NOTICE or PRIVMSG.
+    this.sendMessage(aIsNotice ? "NOTICE" : "PRIVMSG", [aTarget, ircParam]);
+  },
+
+  // Implement section 3.1 of RFC 2812
+  _connectionRegistration: function() {
+    // Send the password message, if provided (section 3.1.1).
+    if (this.imAccount.password) {
+      this.sendMessage("PASS", this.imAccount.password,
+                       "PASS <password not logged>");
+    }
+    // Send the nick message (section 3.1.2).
+    this.sendMessage("NICK", this._nickname);
+
+    // Send the user message (section 3.1.3).
+    // Use brandShortName as the username.
+    let username =
+      l10nHelper("chrome://branding/locale/brand.properties")("brandShortName");
+    this.sendMessage("USER", [username, this._mode.toString(), "*",
+                              this._realname || this._nickname]);
+  },
+
+  gotDisconnected: function(aError, aErrorMessage) {
+    if (!this.imAccount || this.disconnected)
+       return;
+
+    if (aError === undefined)
+      aError = Ci.prplIAccount.NO_ERROR;
+    // If we are already disconnecting, this call to gotDisconnected
+    // is when the server acknowledges our disconnection.
+    // Otherwise it's because we lost the connection.
+    if (!this.disconnecting)
+      this.reportDisconnecting(aError, aErrorMessage);
+    this._socket.disconnect();
+    delete this._socket;
+
+    clearTimeout(this._isOnTimer);
+    delete this._isOnTimer;
+
+    // Clean up each conversation: mark as left and remove participant.
+    for (let conversation in this._conversations) {
+      if (this.isMUCName(conversation)) {
+        // Remove the user's nick and mark the conversation as left as that's
+        // the final known state of the room.
+        this._conversations[conversation].removeParticipant(this._nickname, true);
+        this._conversations[conversation].left = true;
+      }
+    }
+
+    // Mark all contacts on the account as having an unknown status.
+    for each (let buddy in this._buddies)
+      buddy.setStatus(Ci.imIStatusInfo.STATUS_UNKNOWN, "");
+
+    this.reportDisconnected();
+  },
+
+  unInit: function() {
+    // Disconnect if we're online while this gets called.
+    if (this._socket) {
+      if (!this.disconnecting)
+        this.quit();
+      this._socket.disconnect();
+    }
+    delete this.imAccount;
+    clearTimeout(this._isOnTimer);
+    clearTimeout(this._quitTimer);
+  }
+};
+
+function ircProtocol() {
+  // ircCommands.jsm exports one variable: commands. Import this directly into
+  // the protocol object.
+  Cu.import("resource:///modules/ircCommands.jsm", this);
+  this.registerCommands();
+
+  // Register the standard handlers
+  let tempScope = {};
+  Cu.import("resource:///modules/ircBase.jsm", tempScope);
+  Cu.import("resource:///modules/ircISUPPORT.jsm", tempScope);
+  Cu.import("resource:///modules/ircCTCP.jsm", tempScope);
+  Cu.import("resource:///modules/ircDCC.jsm", tempScope);
+
+  // Register default IRC handlers (IRC base, CTCP).
+  ircHandlers.registerHandler(tempScope.ircBase);
+  ircHandlers.registerHandler(tempScope.ircISUPPORT);
+  ircHandlers.registerHandler(tempScope.ircCTCP);
+  // Register default CTCP handlers (CTCP base, DCC).
+  ircHandlers.registerCTCPHandler(tempScope.ctcpBase);
+  ircHandlers.registerCTCPHandler(tempScope.ctcpDCC);
+}
+ircProtocol.prototype = {
+  __proto__: GenericProtocolPrototype,
+  get name() "IRC",
+  get iconBaseURI() "chrome://prpl-irc/skin/",
+  get baseId() "prpl-irc",
+
+  get usernameEmptyText() _("usernameHint"),
+  usernameSplits: [
+    {label: _("options.server"), separator: "@",
+     defaultValue: "chat.freenode.net", reverse: true}
+  ],
+
+  options: {
+    // TODO Default to IRC over SSL.
+    "port": {label: _("options.port"), default: 6667},
+    "ssl": {label: _("options.ssl"), default: false},
+    // TODO We should attempt to auto-detect encoding instead.
+    "encoding": {label: _("options.encoding"), default: "UTF-8"},
+    "quitmsg": {label: _("options.quitMessage"),
+                get default() Services.prefs.getCharPref("chat.irc.defaultQuitMessage")},
+    "partmsg": {label: _("options.partMessage"), default: ""},
+    "showServerTab": {label: _("options.showServerTab"), default: false}
+  },
+
+  get chatHasTopic() true,
+  get slashCommandsNative() true,
+  //  Passwords in IRC are optional, and are needed for certain functionality.
+  get passwordOptional() true,
+
+  getAccount: function(aImAccount) new ircAccount(this, aImAccount),
+  classID: Components.ID("{607b2c0b-9504-483f-ad62-41de09238aec}")
+};
+
+const NSGetFactory = XPCOMUtils.generateNSGetFactory([ircProtocol]);
new file mode 100644
--- /dev/null
+++ b/chat/protocols/irc/irc.manifest
@@ -0,0 +1,3 @@
+component {607b2c0b-9504-483f-ad62-41de09238aec} irc.js
+contract @mozilla.org/chat/irc;1 {607b2c0b-9504-483f-ad62-41de09238aec}
+category im-protocol-plugin prpl-irc @mozilla.org/chat/irc;1
new file mode 100644
--- /dev/null
+++ b/chat/protocols/irc/ircBase.jsm
@@ -0,0 +1,1217 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Instantbird.
+ *
+ * The Initial Developer of the Original Code is
+ * Patrick Cloke <clokep@gmail.com>.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * This contains the implementation for the basic Internet Relay Chat (IRC)
+ * protocol covered by RFCs 2810, 2811, 2812 and 2813 (which obsoletes RFC
+ * 1459). RFC 2812 covers the client commands and protocol.
+ *   RFC 2810: Internet Relay Chat: Architecture
+ *     http://tools.ietf.org/html/rfc2810
+ *   RFC 2811: Internet Relay Chat: Channel Management
+ *     http://tools.ietf.org/html/rfc2811
+ *   RFC 2812: Internet Relay Chat: Client Protocol
+ *     http://tools.ietf.org/html/rfc2812
+ *   RFC 2813: Internet Relay Chat: Server Protocol
+ *     http://tools.ietf.org/html/rfc2813
+ *   RFC 1459: Internet Relay Chat Protocol
+ *     http://tools.ietf.org/html/rfc1459
+ */
+const EXPORTED_SYMBOLS = ["ircBase"];
+
+const {interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource:///modules/imXPCOMUtils.jsm");
+Cu.import("resource:///modules/imServices.jsm");
+Cu.import("resource:///modules/ircHandlers.jsm");
+Cu.import("resource:///modules/ircUtils.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "DownloadUtils", function() {
+  Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
+  return DownloadUtils;
+});
+
+function privmsg(aAccount, aMessage, aIsNotification) {
+  let params = {incoming: true};
+  if (aIsNotification)
+    params.notification = true;
+  aAccount.getConversation(aAccount.isMUCName(aMessage.params[0]) ?
+                           aMessage.params[0] : aMessage.nickname)
+          .writeMessage(aMessage.nickname || aMessage.source,
+                        aMessage.params[1], params);
+  return true;
+}
+
+// Display the message and remove them from the rooms they're in.
+function leftRoom(aAccount, aNicks, aChannels, aSource, aReason, aKicked) {
+  let msgId = aKicked ? "kicked" : "parted";
+  // If a part message was included, include it.
+  let reason = aReason ? _("message." + msgId + ".reason", aReason) : "";
+  function __(aYou, aNick) {
+    // If the user is kicked, we need to say who kicked them.
+    if (aKicked)
+      return _("message." + msgId + aYou, aNick, aSource, reason);
+    return _("message." + msgId + aYou, aNick, reason);
+  }
+
+  for each (let channelName in aChannels) {
+    for each (let nick in aNicks) {
+      if (!aAccount.hasConversation(channelName))
+        continue; // Handle when we closed the window
+      let conversation = aAccount.getConversation(channelName);
+
+      let msg;
+      if (aAccount.normalize(nick) == aAccount.normalize(aAccount._nickname)) {
+        msg = __(".you", reason);
+        // If the user left, mark the conversation as no longer being active.
+        conversation.left = true;
+        conversation.notifyObservers(conversation, "update-conv-chatleft");
+      }
+      else
+        msg = __("", nick);
+
+      conversation.writeMessage(aSource, msg, {system: true});
+      conversation.removeParticipant(nick, true);
+    }
+  }
+  return true;
+}
+
+function writeMessage(aAccount, aMessage, aString, aType) {
+  let type = {};
+  type[aType] = true;
+  aAccount.getConversation(aMessage.source)
+          .writeMessage(aMessage.source, aString, type);
+  return true;
+}
+
+// If aNoLastParam is true, the last parameter is not printed out.
+function serverMessage(aAccount, aMsg, aNoLastParam) {
+  // If we don't want to show messages from the server, just mark it as handled.
+  if (!aAccount._showServerTab)
+    return true;
+
+  return writeMessage(aAccount, aMsg,
+                      aMsg.params.slice(1, aNoLastParam ? -1 : undefined).join(" "),
+                      "system");
+}
+
+function errorMessage(aAccount, aMessage, aError)
+  writeMessage(aAccount, aMessage, aError, "error")
+
+function setWhoIs(aAccount, aMessage, aFields) {
+  let buddyName = aAccount.normalize(aMessage.params[1], aAccount.userPrefixes);
+  // If the buddy isn't in the list yet, add it.
+  if (!hasOwnProperty(aAccount.whoisInformation, buddyName))
+    aAccount.whoisInformation[buddyName] = {};
+
+  // Set the WHOIS fields.
+  for (let field in aFields)
+    aAccount.whoisInformation[buddyName][field] = aFields[field];
+
+  return true;
+}
+
+// Try a new nick if the previous tried nick is already in use.
+function tryNewNick(aAccount, aMessage) {
+  // Take the returned nick and increment the last character.
+  aAccount._nickname = aMessage.params[1].slice(0, -1) +
+    String.fromCharCode(
+      aMessage.params[1].charCodeAt(aMessage.params[1].length - 1) + 1
+    );
+  // Inform the user.
+  LOG(aMessage.params[1] + " is already in use, trying " + aAccount._nickname);
+
+  aAccount.sendMessage("NICK", aAccount._nickname); // Nick message.
+  return true;
+}
+
+// See RFCs 2811 & 2812 (which obsoletes RFC 1459) for a description of these
+// commands.
+var ircBase = {
+  // Parameters
+  name: "RFC 2812", // Name identifier
+  priority: ircHandlers.DEFAULT_PRIORITY,
+
+  // The IRC commands that can be handled.
+  commands: {
+    "ERROR": function(aMessage) {
+      // ERROR <error message>
+      // Client connection has been terminated.
+      clearTimeout(this._quitTimer);
+      this.gotDisconnected(Ci.prplIAccount.NO_ERROR,
+                           aMessage.params[0]); // Notify account manager.
+      return true;
+    },
+    "INVITE": function(aMessage) {
+      // INVITE  <nickname> <channel>
+      // TODO prompt user to join channel.
+      return false;
+    },
+    "JOIN": function(aMessage) {
+      // JOIN ( <channel> *( "," <channel> ) [ <key> *( "," <key> ) ] ) / "0"
+      // Add the buddy to each channel
+      for each (let channelName in aMessage.params[0].split(",")) {
+        let conversation = this.getConversation(channelName);
+        if (this.normalize(aMessage.nickname, this.userPrefixes) ==
+            this.normalize(this._nickname)) {
+          // If you join, clear the participants list to avoid errors w/
+          // repeated participants.
+          conversation.removeAllParticipants();
+          conversation.left = false;
+          conversation.notifyObservers(conversation, "update-conv-chatleft");
+        }
+        else {
+          // Don't worry about adding ourself, RPL_NAMES takes care of that
+          // case.
+          conversation.getParticipant(aMessage.nickname, true);
+          let msg = _("message.join", aMessage.nickname, aMessage.source);
+          conversation.writeMessage(aMessage.nickname, msg, {system: true,
+                                                             noLinkification: true});
+        }
+      }
+      return true;
+    },
+    "KICK": function(aMessage) {
+      // KICK <channel> *( "," <channel> ) <user> *( "," <user> ) [<comment>]
+      return leftRoom(this, aMessage.params[1].split(","),
+                      aMessage.params[0].split(","), aMessage.nickname,
+                      aMessage.params.length == 3 ? aMessage.params[2] : null,
+                      true);
+    },
+    "MODE": function(aMessage) {
+      // MODE <nickname> *( ( "+" / "-") *( "i" / "w" / "o" / "O" / "r" ) )
+      // If less than 3 parameter is given, the mode is your usermode.
+      if (aMessage.params.length >= 3) {
+        // Update the mode of the ConvChatBuddy.
+        let conversation = this.getConversation(aMessage.params[0]);
+        let convChatBuddy = conversation.getParticipant(aMessage.params[2]);
+        convChatBuddy.setMode(aMessage.params[1]);
+
+        // Notify the UI of changes.
+        let msg = _("message.mode", aMessage.params[1], aMessage.params[2],
+                    aMessage.nickname);
+        conversation.writeMessage(aMessage.nickname, msg, {system: true});
+        conversation.notifyObservers(convChatBuddy, "chat-buddy-update");
+      }
+      return true;
+    },
+    "NICK": function(aMessage) {
+      // NICK <nickname>
+      this.changeBuddyNick(aMessage.nickname, aMessage.params[0]);
+      return true;
+    },
+    "NOTICE": function(aMessage) {
+      // NOTICE <msgtarget> <text>
+      // If the message doesn't have a nickname, it's from the server, don't
+      // show it unless the user wants to see it.
+      if (!aMessage.hasOwnProperty("nickname"))
+        return serverMessage(this, aMessage);
+      return privmsg(this, aMessage, true);
+    },
+    "PART": function(aMessage) {
+      // PART <channel> *( "," <channel> ) [ <Part Message> ]
+      return leftRoom(this, [aMessage.nickname], aMessage.params[0].split(","),
+                      aMessage.source,
+                      aMessage.params.length == 2 ? aMessage.params[1] : null);
+    },
+    "PING": function(aMessage) {
+      // PING <server1 [ <server2> ]
+      // Keep the connection alive
+      this.sendMessage("PONG", aMessage.params[0]);
+      return true;
+    },
+    "PRIVMSG": function(aMessage) {
+      // PRIVMSG <msgtarget> <text to be sent>
+      // Display message in conversation
+      return privmsg(this, aMessage);
+    },
+    "QUIT": function(aMessage) {
+      // QUIT [ < Quit Message> ]
+      // Some IRC servers automatically prefix a "Quit: " string. Remove the
+      // duplication and use a localized version.
+      let quitMsg = aMessage.params[0];
+      if (quitMsg.indexOf("Quit: ") == 0)
+        quitMsg = quitMsg.slice(6); // "Quit: ".length
+      // If a quit message was included, show it.
+      let msg = _("message.quit", aMessage.nickname,
+                  aMessage.params.length ? _("message.quit2", quitMsg) : "");
+      // Loop over every conversation with the user and display that they quit.
+      for each (let conversation in this._conversations) {
+        if (conversation.isChat &&
+            conversation.hasParticipant(aMessage.nickname)) {
+          conversation.writeMessage(aMessage.source, msg, {system: true});
+          conversation.removeParticipant(aMessage.nickname, true);
+        }
+      }
+      return true;
+    },
+    "SQUIT": function(aMessage) {
+      // XXX do we need this?
+      return false;
+    },
+    "TOPIC": function(aMessage) {
+      // TOPIC <channel> [ <topic> ]
+      // Show topic as a message.
+      let source = aMessage.nickname || aMessage.source;
+      let conversation = this.getConversation(aMessage.params[0]);
+      let topic = aMessage.params[1];
+      let message;
+      if (topic)
+        message = _("message.topicChanged", source, topic);
+      else
+        message = _("message.topicCleared", source);
+      conversation.writeMessage(source, message, {system: true});
+      // Set the topic in the conversation and update the UI.
+      conversation.setTopic(topic ? ctcpFormatToText(topic) : "", source);
+      return true;
+    },
+    "001": function(aMessage) { // RPL_WELCOME
+      // Welcome to the Internet Relay Network <nick>!<user>@<host>
+      this.reportConnected();
+      // Check if any of our buddies are online!
+      this.sendIsOn();
+      return serverMessage(this, aMessage);
+    },
+    "002": function(aMessage) { // RPL_YOURHOST
+      // Your host is <servername>, running version <ver>
+      return serverMessage(this, aMessage);
+    },
+    "003": function(aMessage) { // RPL_CREATED
+      // This server was created <date>
+      // TODO parse this date and keep it for some reason? Do we care?
+      return serverMessage(this, aMessage);
+    },
+    "004": function(aMessage) { // RPL_MYINFO
+      // <servername> <version> <available user modes> <available channel modes>
+      // TODO parse the available modes, let the UI respond and inform the user
+      return serverMessage(this, aMessage);
+    },
+    "005": function(aMessage) { // RPL_BOUNCE
+      // Try server <server name>, port <port number>
+      return serverMessage(this, aMessage);
+    },
+
+    /*
+     * Handle response to TRACE message
+     */
+    "200": function(aMessage) { // RPL_TRACELINK
+      // Link <version & debug level> <destination> <next server>
+      // V<protocol version> <link updateime in seconds> <backstream sendq>
+      // <upstream sendq>
+      return serverMessage(this, aMessage);
+    },
+    "201": function(aMessage) { // RPL_TRACECONNECTING
+      // Try. <class> <server>
+      return serverMessage(this, aMessage);
+    },
+    "202": function(aMessage) { // RPL_TRACEHANDSHAKE
+      // H.S. <class> <server>
+      return serverMessage(this, aMessage);
+    },
+    "203": function(aMessage) { // RPL_TRACEUNKNOWN
+      // ???? <class> [<client IP address in dot form>]
+      return serverMessage(this, aMessage);
+    },
+    "204": function(aMessage) { // RPL_TRACEOPERATOR
+      // Oper <class> <nick>
+      return serverMessage(this, aMessage);
+    },
+    "205": function(aMessage) { // RPL_TRACEUSER
+      // User <class> <nick>
+      return serverMessage(this, aMessage);
+    },
+    "206": function(aMessage) { // RPL_TRACESERVER
+      // Serv <class> <int>S <int>C <server> <nick!user|*!*>@<host|server>
+      // V<protocol version>
+      return serverMessage(this, aMessage);
+    },
+    "207": function(aMessage) { // RPL_TRACESERVICE
+      // Service <class> <name> <type> <active type>
+      return serverMessage(this, aMessage);
+    },
+    "208": function(aMessage) { // RPL_TRACENEWTYPE
+      // <newtype> 0 <client name>
+      return serverMessage(this, aMessage);
+    },
+    "209": function(aMessage) { // RPL_TRACECLASS
+      // Class <class> <count>
+      return serverMessage(this, aMessage);
+    },
+    "210": function(aMessage) { // RPL_TRACERECONNECTION
+      // Unused.
+      return serverMessage(this, aMessage);
+    },
+
+    /*
+     * Handle stats messages.
+     **/
+    "211": function(aMessage) { // RPL_STATSLINKINFO
+      // <linkname> <sendq> <sent messages> <sent Kbytes> <received messages>
+      // <received Kbytes> <time open>
+      return serverMessage(this, aMessage);
+    },
+    "212": function(aMessage) { // RPL_STATSCOMMAND
+      // <command> <count> <byte count> <remote count>
+      return serverMessage(this, aMessage);
+    },
+    "213": function(aMessage) { // RPL_STATSCLINE
+      // Non-generic
+      return serverMessage(this, aMessage);
+    },
+    "214": function(aMessage) { // RPL_STATSNLINE
+      // Non-generic
+      return serverMessage(this, aMessage);
+    },
+    "215": function(aMessage) { // RPL_STATSILINE
+      // Non-generic
+      return serverMessage(this, aMessage);
+    },
+    "216": function(aMessage) { // RPL_STATSKLINE
+      // Non-generic
+      return serverMessage(this, aMessage);
+    },
+    "217": function(aMessage) { // RPL_STATSQLINE
+      // Non-generic
+      return serverMessage(this, aMessage);
+    },
+    "218": function(aMessage) { // RPL_STATSYLINE
+      // Non-generic
+      return serverMessage(this, aMessage);
+    },
+    "219": function(aMessage) { // RPL_ENDOFSTATS
+      // <stats letter> :End of STATS report
+      return serverMessage(this, aMessage);
+    },
+
+    "221": function(aMessage) { // RPL_UMODEIS
+      // <user mode string>
+      // TODO track and update the UI accordingly.
+      return false;
+    },
+
+    /*
+     * Services
+     */
+    "231": function(aMessage) { // RPL_SERVICEINFO
+      // Non-generic
+      return serverMessage(this, aMessage);
+    },
+    "232": function(aMessage) { // RPL_ENDOFSERVICES
+      // Non-generic
+      return serverMessage(this, aMessage);
+    },
+    "233": function(aMessage) { // RPL_SERVICE
+      // Non-generic
+      return serverMessage(this, aMessage);
+    },
+
+    /*
+     * Server
+     */
+    "234": function(aMessage) { // RPL_SERVLIST
+      // <name> <server> <mask> <type> <hopcount> <info>
+      return serverMessage(this, aMessage);
+    },
+    "235": function(aMessage) { // RPL_SERVLISTEND
+      // <mask> <type> :End of service listing
+      return serverMessage(this, aMessage, true);
+    },
+
+    /*
+     * Stats
+     * TODO some of these have real information we could try to parse.
+     */
+    "240": function(aMessage) { // RPL_STATSVLINE
+      // Non-generic
+      return serverMessage(this, aMessage);
+    },
+    "241": function(aMessage) { // RPL_STATSLLINE
+      // Non-generic
+      return serverMessage(this, aMessage);
+    },
+    "242": function(aMessage) { // RPL_STATSUPTIME
+      // :Server Up %d days %d:%02d:%02d
+      return serverMessage(this, aMessage);
+    },
+    "243": function(aMessage) { // RPL_STATSOLINE
+      // O <hostmask> * <name>
+      return serverMessage(this, aMessage);
+    },
+    "244": function(aMessage) { // RPL_STATSHLINE
+      // Non-generic
+      return serverMessage(this, aMessage);
+    },
+    "245": function(aMessage) { // RPL_STATSSLINE
+      // Non-generic
+      // Note that this is given as 244 in RFC 2812, this seems to be incorrect.
+      return serverMessage(this, aMessage);
+    },
+    "246": function(aMessage) { // RPL_STATSPING
+      // Non-generic
+      return serverMessage(this, aMessage);
+    },
+    "247": function(aMessage) { // RPL_STATSBLINE
+      // Non-generic
+      return serverMessage(this, aMessage);
+    },
+    "250": function(aMessage) { // RPL_STATSDLINE
+      // Non-generic
+      return serverMessage(this, aMessage);
+    },
+
+    /*
+     * LUSER messages
+     */
+    "251": function(aMessage) { // RPL_LUSERCLIENT
+      // :There are <integer> users and <integer> services on <integer> servers
+      return serverMessage(this, aMessage);
+    },
+    "252": function(aMessage) { // RPL_LUSEROP, 0 if not sent
+      // <integer> :operator(s) online
+      return serverMessage(this, aMessage);
+    },
+    "253": function(aMessage) { // RPL_LUSERUNKNOWN, 0 if not sent
+      // <integer> :unknown connection(s)
+      return serverMessage(this, aMessage);
+    },
+    "254": function(aMessage) { // RPL_LUSERCHANNELS, 0 if not sent
+      // <integer> :channels formed
+      return serverMessage(this, aMessage);
+    },
+    "255": function(aMessage) { // RPL_LUSERME
+      // :I have <integer> clients and <integer> servers
+      return serverMessage(this, aMessage);
+    },
+
+    /*
+     * ADMIN messages
+     */
+    "256": function(aMessage) { // RPL_ADMINME
+      // <server> :Administrative info
+      return serverMessage(this, aMessage);
+    },
+    "257": function(aMessage) { // RPL_ADMINLOC1
+      // :<admin info>
+      // City, state & country
+      return serverMessage(this, aMessage);
+    },
+    "258": function(aMessage) { // RPL_ADMINLOC2
+      // :<admin info>
+      // Institution details
+      return serverMessage(this, aMessage);
+    },
+    "259": function(aMessage) { // RPL_ADMINEMAIL
+      // :<admin info>
+      // TODO We could parse this for a contact email.
+      return serverMessage(this, aMessage);
+    },
+
+    /*
+     * TRACELOG
+     */
+    "261": function(aMessage) { // RPL_TRACELOG
+      // File <logfile> <debug level>
+      return serverMessage(this, aMessage);
+    },
+    "262": function(aMessage) { // RPL_TRACEEND
+      // <server name> <version & debug level> :End of TRACE
+      return serverMessage(this, aMessage, true);
+    },
+
+    /*
+     * Try again.
+     */
+    "263": function(aMessage) { // RPL_TRYAGAIN
+      // <command> :Please wait a while and try again.
+      // TODO setTimeout for a minute or so and try again?
+      return false;
+    },
+
+    "265": function(aMessage) { // nonstandard
+      // :Current Local Users: <integer>  Max: <integer>
+      return serverMessage(this, aMessage);
+    },
+    "266": function(aMessage) { // nonstandard
+      // :Current Global Users: <integer>  Max: <integer>
+      return serverMessage(this, aMessage);
+    },
+    "300": function(aMessage) { // RPL_NONE
+      // Non-generic
+      return serverMessage(this, aMessage);
+    },
+
+    /*
+     * Status messages
+     */
+    "301": function(aMessage) { // RPL_AWAY
+      // <nick> :<away message>
+      // TODO set user as away on buddy list / conversation lists
+      // TODO Display an autoResponse if this is after sending a private message
+      return setWhoIs(this, aMessage, {away: aMessage.params[2]});
+    },
+    "302": function(aMessage) { // RPL_USERHOST
+      // :*1<reply> *( " " <reply )"
+      // reply = nickname [ "*" ] "=" ( "+" / "-" ) hostname
+      // TODO Can tell op / away from this
+      return false;
+    },
+    "303": function(aMessage) { // RPL_ISON
+      // :*1<nick> *( " " <nick> )"
+      // If there are any buddies online, mark them as online, otherwise return
+      // early.
+      if (aMessage.params.length <= 1)
+        return true;
+
+      // The buddy names as returned by the server.
+      let receivedBuddyNames = aMessage.params[1].trim().split(" ");
+      // This was received in response to the last ISON message sent.
+      for each (let buddyName in this.pendingIsOnQueue) {
+        // If the buddy name is in the list returned from the server, they're
+        // online.
+        let status = (receivedBuddyNames.indexOf(buddyName) == -1) ?
+                       Ci.imIStatusInfo.STATUS_OFFLINE :
+                       Ci.imIStatusInfo.STATUS_AVAILABLE;
+
+        // Set the status with no status message, only if the buddy actually
+        // exists in the buddy list.
+        let buddy = this.getBuddy(buddyName);
+        if (buddy)
+          buddy.setStatus(status, "");
+      }
+      return true;
+    },
+    "305": function(aMessage) { // RPL_UNAWAY
+      // :You are no longer marked as being away
+      this.isAway = false;
+      return true;
+    },
+    "306": function(aMessage) { // RPL_NOWAWAY
+      // :You have been marked as away
+      this.isAway = true;
+      return true;
+    },
+
+    /*
+     * WHOIS
+     */
+    "311": function(aMessage) { // RPL_WHOISUSER
+      // <nick> <user> <host> * :<real name>
+      // <username>@<hostname>
+      let source = aMessage.params[2] + "@" + aMessage.params[3];
+      return setWhoIs(this, aMessage, {connectedFrom: source,
+                                       realname: aMessage.params[5]});
+    },
+    "312": function(aMessage) { // RPL_WHOISSERVER
+      // <nick> <server> :<server info>
+      return setWhoIs(this, aMessage,
+                      {server: _("tooltip.serverValue", aMessage.params[2],
+                                 aMessage.params[3])});
+    },
+    "313": function(aMessage) { // RPL_WHOISOPERATOR
+      // <nick> :is an IRC operator
+      return setWhoIs(this, aMessage, {ircOp: true});
+    },
+    "314": function(aMessage) { // RPL_WHOWASUSER
+      // <nick> <user> <host> * :<real name>
+      return setWhoIs(this, aMessage, {user: aMessage.params[2],
+                                       host: aMessage.params[3],
+                                       realname: aMessage.params[5]});
+    },
+    "315": function(aMessage) { // RPL_ENDOFWHO
+      // <name> :End of WHO list
+      return false;
+    },
+    "316": function(aMessage) { // RPL_WHOISCHANOP
+      // Non-generic
+      return false;
+    },
+    "317": function(aMessage) { // RPL_WHOISIDLE
+      // <nick> <integer> :seconds idle
+      let valuesAndUnits =
+        DownloadUtils.convertTimeUnits(parseInt(aMessage.params[2]));
+      if (!valuesAndUnits[2])
+        valuesAndUnits.splice(2, 2);
+      return setWhoIs(this, aMessage, {idleTime: valuesAndUnits.join(" ")});
+    },
+    "318": function(aMessage) { // RPL_ENDOFWHOIS
+      // <nick> :End of WHOIS list
+      // We've received everything about WHOIS, tell the tooltip that is waiting
+      // for this information.
+      let buddyName = this.normalize(aMessage.params[1]);
+
+      // Notify the tooltip.
+      Services.obs.notifyObservers(this.getBuddyInfo(buddyName),
+                                   "user-info-received", buddyName);
+      return true;
+    },
+    "319": function(aMessage) { // RPL_WHOISCHANNELS
+      // <nick> :*( ( "@" / "+" ) <channel> " " )
+      return setWhoIs(this, aMessage, {channels: aMessage.params[2]});
+    },
+
+    /*
+     * LIST
+     */
+    "321": function(aMessage) { // RPL_LISTSTART
+      // Obsolete. Not used.
+      return false;
+    },
+    "322": function(aMessage) { // RPL_LIST
+      // <channel> <# visible> :<topic>
+      // TODO parse this for # users & topic.
+      return serverMessage(this, aMessage);
+    },
+    "323": function(aMessage) { // RPL_LISTEND
+      // :End of LIST
+      return true;
+    },
+
+    /*
+     * Channel functions
+     */
+    "324": function(aMessage) { // RPL_CHANNELMODEIS
+      // <channel> <mode> <mode params>
+      // TODO parse this and have the UI respond accordingly.
+      return false;
+    },
+    "325": function(aMessage) { // RPL_UNIQOPIS
+      // <channel> <nickname>
+      // TODO parse this and have the UI respond accordingly.
+      return false;
+    },
+    "331": function(aMessage) { // RPL_NOTOPIC
+      // <channel> :No topic is set
+      let conversation = this.getConversation(aMessage.params[1]);
+      conversation.setTopic(""); // Clear the topic.
+      let msg = _("message.topicRemoved", conversation.name);
+      conversation.writeMessage(null, msg, {system: true});
+      return false;
+    },
+    "332": function(aMessage) { // RPL_TOPIC
+      // <channel> :<topic>
+      // Update the topic UI
+      let conversation = this.getConversation(aMessage.params[1]);
+      conversation.setTopic(ctcpFormatToText(aMessage.params[2]));
+      // Send the topic as a message.
+      let msg = _("message.topic", conversation.name, aMessage.params[2]);
+      conversation.writeMessage(null, msg, {system: true});
+      return true;
+    },
+    "333": function(aMessage) { // nonstandard
+      // <channel> <nickname> <time>
+      return true;
+    },
+
+    /*
+     * Invitations
+     */
+    "341": function(aMessage) { // RPL_INVITING
+      // <channel> <nick>
+      return serverMessage(this, aMessage,
+                           _("message.invited", aMessage.params[1],
+                             aMessage.params[0]));
+    },
+    "342": function(aMessage) { // RPL_SUMMONING
+      // <user> :Summoning user to IRC
+      return writeMessage(this, aMessage,
+                          _("message.summoned", aMessage.params[0]));
+    },
+    "346": function(aMessage) { // RPL_INVITELIST
+      // <chanel> <invitemask>
+      // TODO what do we do?
+      return false;
+    },
+    "347": function(aMessage) { // RPL_ENDOFINVITELIST
+      // <channel> :End of channel invite list
+      // TODO what do we do?
+      return false;
+    },
+    "348": function(aMessage) { // RPL_EXCEPTLIST
+      // <channel> <exceptionmask>
+      // TODO what do we do?
+      return false;
+    },
+    "349": function(aMessage) { // RPL_ENDOFEXCEPTIONLIST
+      // <channel> :End of channel exception list
+      // TODO update UI?
+      return false;
+    },
+
+    /*
+     * Version
+     */
+    "351": function(aMessage) { // RPL_VERSION
+      // <version>.<debuglevel> <server> :<comments>
+      return serverMessage(this, aMessage);
+    },
+
+    /*
+     * WHO
+     */
+    "352": function(aMessage) { // RPL_WHOREPLY
+      // <channel> <user> <host> <server> <nick> ( "H" / "G" ) ["*"] [ ("@" / "+" ) ] :<hopcount> <real name>
+      // TODO parse and display this?
+      return false;
+    },
+
+    /*
+     * NAMREPLY
+     */
+    "353": function(aMessage) { // RPL_NAMREPLY
+      // <target> ( "=" / "*" / "@" ) <channel> :[ "@" / "+" ] <nick> *( " " [ "@" / "+" ] <nick> )
+      // TODO Keep if this is secret (@), private (*) or public (=)
+      let conversation = this.getConversation(aMessage.params[2]);
+      aMessage.params[3].trim().split(" ").forEach(
+        function(aNick) conversation.getParticipant(aNick, false));
+      return true;
+    },
+
+    "361": function(aMessage) { // RPL_KILLDONE
+      // Non-generic
+      // TODO What is this?
+      return false;
+    },
+    "362": function(aMessage) { // RPL_CLOSING
+      // Non-generic
+      // TODO What is this?
+      return false;
+    },
+    "363": function(aMessage) { // RPL_CLOSEEND
+      // Non-generic
+      // TODO What is this?
+      return false;
+    },
+
+    /*
+     * Links.
+     */
+    "364": function(aMessage) { // RPL_LINKS
+      // <mask> <server> :<hopcount> <server info>
+      return serverMessage(this, aMessage);
+    },
+    "365": function(aMessage) { // RPL_ENDOFLINKS
+      // <mask> :End of LINKS list
+      return true;
+    },
+
+    /*
+     * Names
+     */
+    "366": function(aMessage) { // RPL_ENDOFNAMES
+      // <target> <channel> :End of NAMES list
+      // Notify the conversation of the added participants.
+      let conversation = this.getConversation(aMessage.params[1]);
+      conversation.notifyObservers(conversation.getParticipants(),
+                                   "chat-buddy-add");
+      return true;
+    },
+
+    /*
+     * End of a bunch of lists
+     */
+    "367": function(aMessage) { // RPL_BANLIST
+      // <channel> <banmask>
+      // TODO
+      return false;
+    },
+    "368": function(aMessage) { // RPL_ENDOFBANLIST
+      // <channel> :End of channel ban list
+      // TODO
+      return false;
+    },
+    "369": function(aMessage) { // RPL_ENDOFWHOWAS
+      // <nick> :End of WHOWAS
+      // TODO
+      return false;
+    },
+
+    /*
+     * Server info
+     */
+    "371": function(aMessage) { // RPL_INFO
+      // :<string>
+      return serverMessage(this, aMessage);
+    },
+    "372": function(aMessage) { // RPL_MOTD
+      // :- <text>
+      this._motd.push(aMessage.params[1].slice(2));
+      return true;
+    },
+    "373": function(aMessage) { // RPL_INFOSTART
+      // Non-generic
+      // This is unnecessary and servers just send RPL_INFO.
+      return true;
+    },
+    "374": function(aMessage) { // RPL_ENDOFINFO
+      // :End of INFO list
+      return true;
+    },
+    "375": function(aMessage) { // RPL_MOTDSTART
+      // :- <server> Message of the day -
+      this._motd = [aMessage.params[1].slice(2, -2)];
+      return true;
+    },
+    "376": function(aMessage) { // RPL_ENDOFMOTD
+      // :End of MOTD command
+      if (this._showServerTab)
+        writeMessage(this, aMessage, this._motd.join("\n"), "incoming");
+      delete this._motd;
+      return true;
+    },
+
+    /*
+     * OPER
+     */
+    "381": function(aMessage) { // RPL_YOUREOPER
+      // :You are now an IRC operator
+      // TODO update UI accordingly to show oper status
+      return serverMessage(this, aMessage);
+    },
+    "382": function(aMessage) { // RPL_REHASHING
+      // <config file> :Rehashing
+      return serverMessage(this, aMessage);
+    },
+    "383": function(aMessage) { // RPL_YOURESERVICE
+      // You are service <servicename>
+      WARN("Received \"You are a service\" message.");
+      return true;
+    },
+
+    /*
+     * Info
+     */
+    "384": function(aMessage) { // RPL_MYPORTIS
+      // Non-generic
+      // TODO Parse and display?
+      return false;
+    },
+    "391": function(aMessage) { // RPL_TIME
+      // <server> :<string showing server's local time>
+      // TODO parse date string & store or just show it?
+      return false;
+    },
+    "392": function(aMessage) { // RPL_USERSSTART
+      // :UserID   Terminal  Host
+      // TODO
+      return false;
+    },
+    "393": function(aMessage) { // RPL_USERS
+      // :<username> <ttyline> <hostname>
+      // TODO store into buddy list? List out?
+      return false;
+    },
+    "394": function(aMessage) { // RPL_ENDOFUSERS
+      // :End of users
+      // TODO Notify observers of the buddy list?
+      return false;
+    },
+    "395": function(aMessage) { // RPL_NOUSERS
+      // :Nobody logged in
+      // TODO clear buddy list?
+      return false;
+    },
+
+      // Error messages, Implement Section 5.2 of RFC 2812
+    "401": function(aMessage) { // ERR_NOSUCHNICK
+      // <nickname> :No such nick/channel
+      // TODO Parse & display an error to the user.
+      return false;
+    },
+    "402": function(aMessage) { // ERR_NOSUCHSERVER
+      // <server name> :No such server
+      // TODO Parse & display an error to the user.
+      return false;
+    },
+    "403": function(aMessage) { // ERR_NOSUCHCHANNEL
+      // <channel name> :No such channel
+      return errorMessage(this, aMessage,
+                          _("error.noChannel", aMessage.params[0]));
+    },
+    "404": function(aMessage) { // ERR_CANNONTSENDTOCHAN
+      // <channel name> :Cannot send to channel
+      // TODO handle that the channel didn't receive the message.
+      return false;
+    },
+    "405": function(aMessage) { // ERR_TOOMANYCHANNELS
+      // <channel name> :You have joined too many channels
+      return errorMessage(this, aMessage,
+                          _("error.tooManyChannels", aMessage.params[0]));
+    },
+    "406": function(aMessage) { // ERR_WASNOSUCHNICK
+      // <nickname> :There was no such nickname
+      // TODO Error saying the nick never existed.
+      return false;
+    },
+    "407": function(aMessage) { // ERR_TOOMANYTARGETS
+      // <target> :<error code> recipients. <abord message>
+      // TODO
+      return false;
+    },
+    "408": function(aMessage) { // ERR_NOSUCHSERVICE
+      // <service name> :No such service
+      // TODO
+      return false;
+    },
+    "409": function(aMessage) { // ERR_NOORIGIN
+      // :No origin specified
+      // TODO failed PING/PONG message, this should never occur?
+      return false;
+    },
+    "411": function(aMessage) { // ERR_NORECIPIENT
+      // :No recipient given (<command>)
+      ERROR("ERR_NORECIPIENT:\n" + aMessage.params[0]);
+      return true;
+    },
+    "412": function(aMessage) { // ERR_NOTEXTTOSEND
+      // :No text to send
+      // TODO Message was not sent.
+      return true;
+    },
+    "413": function(aMessage) { // ERR_NOTOPLEVEL
+      // <mask> :No toplevel domain specified
+      // TODO Message was not sent.
+      return false;
+    },
+    "414": function(aMessage) { // ERR_WILDTOPLEVEL
+      // <mask> :Wildcard in toplevel domain
+      // TODO Message was not sent.
+      return false;
+    },
+    "415": function(aMessage) { // ERR_BADMASK
+      // <mask> :Bad Server/host mask
+      // TODO Message was not sent.
+      return false;
+    },
+    "421": function(aMessage) { // ERR_UNKNOWNCOMMAND
+      // <command> :Unknown command
+      // TODO This shouldn't occur
+      return false;
+    },
+    "422": function(aMessage) { // ERR_NOMOTD
+      // :MOTD File is missing
+      // No message of the day to display.
+      return true;
+    },
+    "423": function(aMessage) { // ERR_NOADMININFO
+      // <server> :No administrative info available
+      // TODO
+      return false;
+    },
+    "424": function(aMessage) { // ERR_FILEERROR
+      // :File error doing <file op> on <file>
+      // TODO
+      return false;
+    },
+    "431": function(aMessage) { // ERR_NONICKNAMEGIVEN
+      // :No nickname given
+      // TODO
+      return false;
+    },
+    "432": function(aMessage) { // ERR_ERRONEUSNICKNAME
+      // <nick> :Erroneous nickname
+      // TODO Prompt user for new nick? Autoclean characters?
+      return false;
+    },
+    "433": function(aMessage) { // ERR_NICKNAMEINUSE
+      // <nick> :Nickname is already in use
+      return tryNewNick(this, aMessage);
+    },
+    "436": function(aMessage) { // ERR_NICKCOLLISION
+      // <nick> :Nickname collision KILL from <user>@<host>
+      return tryNewNick(this, aMessage);
+    },
+    "437": function(aMessage) { // ERR_UNAVAILRESOURCE
+      // <nick/channel> :Nick/channel is temporarily unavailable
+      // TODO
+      return false;
+    },
+    "441": function(aMessage) { // ERR_USERNOTINCHANNEL
+      // <nick> <channel> :They aren't on that channel
+      // TODO
+      return false;
+    },
+    "442": function(aMessage) { // ERR_NOTONCHANNEL
+      // <channel> :You're not on that channel
+      // TODO
+      return false;
+    },
+    "443": function(aMessage) { // ERR_USERONCHANNEL
+      // <user> <channel> :is already on channel
+      // TODO
+      return false;
+    },
+    "444": function(aMessage) { // ERR_NOLOGIN
+      // <user> :User not logged in
+      // TODO
+      return false;
+    },
+    "445": function(aMessage) { // ERR_SUMMONDISABLED
+      // :SUMMON has been disabled
+      // TODO keep track of this and disable UI associated?
+      return false;
+    },
+    "446": function(aMessage) { // ERR_USERSDISABLED
+      // :USERS has been disabled
+      // TODO Disabled all buddy list etc.
+      return false;
+    },
+    "451": function(aMessage) { // ERR_NOTREGISTERED
+      // :You have not registered
+      // TODO
+      return false;
+    },
+    "461": function(aMessage) { // ERR_NEEDMOREPARAMS
+      // <command> :Not enough parameters
+      // TODO
+      return false;
+    },
+    "462": function(aMessage) { // ERR_ALREADYREGISTERED
+      // :Unauthorized command (already registered)
+      // TODO
+      return false;
+    },
+    "463": function(aMessage) { // ERR_NOPERMFORHOST
+      // :Your host isn't among the privileged
+      // TODO
+      return false;
+    },
+    "464": function(aMessage) { // ERR_PASSWDMISMATCH
+      // :Password incorrect
+      // TODO prompt user for new password
+      return false;
+    },
+    "465": function(aMessage) { // ERR_YOUREBANEDCREEP
+      // :You are banned from this server
+      errorMessage(this, aMessage, _("error.banned"));
+      this.gotDisconnected(Ci.prplIAccount.ERROR_OTHER_ERROR,
+                           _("error.banned")); // Notify account manager.
+      return true;
+    },
+    "466": function(aMessage) { // ERR_YOUWILLBEBANNED
+      return errorMessage(this, aMessage, _("error.bannedSoon"));
+    },
+    "467": function(aMessage) { // ERR_KEYSET
+      // <channel> :Channel key already set
+      // TODO
+      return false;
+    },
+    "471": function(aMessage) { // ERR_CHANNELISFULL
+      // <channel> :Cannot join channel (+l)
+      // TODO
+      return false;
+    },
+    "472": function(aMessage) { // ERR_UNKNOWNMODE
+      // <char> :is unknown mode char to me for <channel>
+      // TODO
+      return false;
+    },
+    "473": function(aMessage) { // ERR_INVITEONLYCHAN
+      // <channel> :Cannot join channel (+i)
+      // TODO
+      return false;
+    },
+    "474": function(aMessage) { // ERR_BANNEDFROMCHAN
+      // <channel> :Cannot join channel (+b)
+      // TODO
+      return false;
+    },
+    "475": function(aMessage) { // ERR_BADCHANNELKEY
+      // <channel> :Cannot join channel (+k)
+      // TODO need to inform the user.
+      return false;
+    },
+    "476": function(aMessage) { // ERR_BADCHANMASK
+      // <channel> :Bad Channel Mask
+      // TODO
+      return false;
+    },
+    "477": function(aMessage) { // ERR_NOCHANMODES
+      // <channel> :Channel doesn't support modes
+      // TODO
+      return false;
+    },
+    "478": function(aMessage) { // ERR_BANLISTFULL
+      // <channel> <char> :Channel list is full
+      // TODO
+      return false;
+    },
+    "481": function(aMessage) { // ERR_NOPRIVILEGES
+      // :Permission Denied- You're not an IRC operator
+      // TODO ask to auth?
+      return false;
+    },
+    "482": function(aMessage) { // ERR_CHANOPRIVSNEEDED
+      // <channel> :You're not channel operator
+      // TODO ask for auth?
+      return false;
+    },
+    "483": function(aMessage) { // ERR_CANTKILLSERVER
+      // :You can't kill a server!
+      // TODO Display error?
+      return false;
+    },
+    "484": function(aMessage) { // ERR_RESTRICTED
+      // :Your connection is restricted!
+      // Indicates user mode +r
+      // TODO
+      return false;
+    },
+    "485": function(aMessage) { // ERR_UNIQOPPRIVSNEEDED
+      // :You're not the original channel operator
+      // TODO ask to auth?
+      return false;
+    },
+    "491": function(aMessage) { // ERR_NOOPERHOST
+      // :No O-lines for your host
+      // TODO
+      return false;
+    },
+    "492": function(aMessage) { //ERR_NOSERVICEHOST
+      // Non-generic
+      // TODO
+      return false;
+    },
+    "501": function(aMessage) { // ERR_UMODEUNKNOWNFLAGS
+      // :Unknown MODE flag
+      // TODO Display error?
+      return false;
+    },
+    "502": function(aMessage) { // ERR_USERSDONTMATCH
+      // :Cannot change mode for other users
+      return errorMessage(this, aMessage, _("error.mode.wrongUser"));
+    }
+  }
+};
new file mode 100644
--- /dev/null
+++ b/chat/protocols/irc/ircCTCP.jsm
@@ -0,0 +1,274 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Instantbird.
+ *
+ * The Initial Developer of the Original Code is
+ * Patrick Cloke <clokep@gmail.com>.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * This implements the Client-to-Client Protocol (CTCP), a subprotocol of IRC.
+ *   REVISED AND UPDATED CTCP SPECIFICATION
+ *     http://www.alien.net.au/irc/ctcp.txt
+ */
+
+const EXPORTED_SYMBOLS = ["ircCTCP", "ctcpBase"];
+
+const Cu = Components.utils;
+
+Cu.import("resource:///modules/imXPCOMUtils.jsm");
+Cu.import("resource:///modules/ircHandlers.jsm");
+Cu.import("resource:///modules/ircUtils.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "PluralForm", function() {
+  Cu.import("resource://gre/modules/PluralForm.jsm");
+  return PluralForm;
+});
+
+function lowLevelDequote(aString) {
+  // Dequote (low level) / Low Level Quoting
+  // Replace quote char \020 followed by 0, n, r or \020 with a null, line
+  // break, carriage return or \020, respectively. Any other character after
+  // \020 is replaced with itself.
+  const replacements = {"0": "\0", "n": "\n", "r": "\r"};
+  return aString.replace(/\x10./g, function(aStr) {
+    return replacements[aStr[1]] || aStr[1];
+  });
+}
+
+function highLevelDequote(aString) {
+  // Dequote (high level) / CTCP Level Quoting
+  // Replace quote char \134 followed by a or \134 with \001 or \134,
+  // respectively. Any other character after \134 is replaced with itself.
+  return aString.replace(/\x5C./g, function(aStr) {
+    return (aStr[1] == "a") ? "\x01" : aStr[1];
+  });
+}
+
+// Split into a CTCP message which is a single command and a single parameter:
+//   <command> " " <parameter>
+// The high level dequote is to unescape \001 in the message content.
+function CTCPMessage(aMessage, aRawCTCPMessage) {
+  let message = aMessage;
+  message.ctcp = {};
+  message.ctcp.rawMessage = aRawCTCPMessage;
+
+  let dequotedCTCPMessage = highLevelDequote(message.ctcp.rawMessage);
+
+  let separator = dequotedCTCPMessage.indexOf(" ");
+  // If there's no space, then only a command is given.
+  // Do not capitalize the command, case sensitive
+  if (separator == -1) {
+    message.ctcp.command = dequotedCTCPMessage;
+    message.ctcp.param = "";
+  }
+  else {
+    message.ctcp.command = dequotedCTCPMessage.slice(0, separator);
+    message.ctcp.param = dequotedCTCPMessage.slice(separator + 1);
+  }
+  return message;
+}
+
+
+// This is the CTCP handler for IRC protocol, it will call each CTCP handler.
+var ircCTCP = {
+  name: "CTCP",
+  // Slightly above default RFC 2812 priority.
+  priority: ircHandlers.HIGH_PRIORITY,
+
+  // CTCP uses only PRIVMSG and NOTICE commands.
+  commands: {
+    "PRIVMSG": ctcpHandleMessage,
+    "NOTICE": ctcpHandleMessage
+  }
+}
+// Parse the message and call all CTCP handlers on the message.
+function ctcpHandleMessage(aMessage) {
+  // If there are no CTCP handlers, then don't parse the CTCP message.
+  if (!ircHandlers.hasCTCPHandlers)
+    return false;
+
+  // The raw CTCP message is in the last parameter of the IRC message.
+  let ctcpRawMessage = lowLevelDequote(aMessage.params.slice(-1)[0]);
+
+  // Split the raw message into the multiple CTCP messages and pull out the
+  // command and parameters.
+  let ctcpMessages = [];
+  let otherMessage = ctcpRawMessage.replace(/\x01([^\x01]*)\x01/g,
+                                            function(aMatch, aMsg) {
+    if (aMsg)
+      ctcpMessages.push(new CTCPMessage(aMessage, aMsg));
+    return "";
+  });
+
+  // If no CTCP messages were found, return false.
+  if (!ctcpMessages.length)
+    return false;
+
+  // If there's some message left, send it back through the IRC handlers after
+  // stripping out the CTCP information. I highly doubt this will ever happen,
+  // but just in case. ;)
+  if (otherMessage) {
+    let message = aMessage;
+    message.params.pop();
+    message.params.push(otherMessage);
+    ircHandlers.handleMessage(message);
+  }
+
+  let handled = true;
+  // Loop over each raw CTCP message.
+  for each (let message in ctcpMessages)
+    handled &= ircHandlers.handleCTCPMessage(this, message);
+
+  return handled;
+}
+
+// This is the the basic CTCP protocol.
+var ctcpBase = {
+  // Parameters
+  name: "CTCP",
+  priority: ircHandlers.DEFAULT_PRIORITY,
+
+  // These represent CTCP commands.
+  commands: {
+    "ACTION": function(aMessage) {
+      // ACTION <text>
+      // Display message in conversation
+      this.getConversation(this.isMUCName(aMessage.params[0]) ?
+                             aMessage.params[0] : aMessage.nickname)
+          .writeMessage(aMessage.nickname || aMessage.source,
+                        "/me " + aMessage.ctcp.param,
+                        {incoming: true});
+      return true;
+    },
+
+    // Used when an error needs to be replied with.
+    "ERRMSG": function(aMessage) {
+      ERROR(aMessage);
+      return false;
+    },
+
+    // Returns the user's full name, and idle time.
+    "FINGER": function(aMessage) false,
+
+    // Dynamic master index of what a client knows.
+    "CLIENTINFO": function(aMessage) false,
+
+    // Used to measure the delay of the IRC network between clients.
+    "PING": function(aMessage) {
+      if (aMessage.command == "PRIVMSG") {
+        // PING timestamp
+        // Received PING request, send PING response.
+        LOG("Received PING request from " + aMessage.nickname +
+            ". Sending PING response: \"" + aMessage.ctcp.param + "\".");
+        this.sendCTCPMessage("PING", aMessage.ctcp.param, aMessage.nickname,
+                              true);
+      }
+      else {
+        // PING timestamp
+        // Received PING response, display to the user.
+        let sentTime = new Date(aMessage.ctcp.param);
+
+        // The received timestamp is invalid
+        if (isNaN(sentTime)) {
+          WARN(aMessage.nickname +
+               " returned an invalid timestamp from a CTCP PING: " +
+               aMessage.ctcp.param);
+          return false;
+        }
+
+        // Find the delay in seconds.
+        let delay = (Date.now() - sentTime) / 1000;
+
+        let message = PluralForm.get(delay, _("ctcp.ping", aMessage.nickname))
+                                .replace("#2", delay);
+        this.getConversation(aMessage.nickname)
+            .writeMessage(aMessage.nickname, message, {system: true});
+      }
+      return true;
+    },
+
+    // An encryption protocol between clients without any known reference.
+    "SED": function(aMessage) false,
+
+    // Where to obtain a copy of a client.
+    "SOURCE": function(aMessage) false,
+
+    // Gets the local date and time from other clients.
+    "TIME": function(aMessage) {
+      if (aMessage.command == "PRIVMSG") {
+        // TIME
+        // Received a TIME request, send a human readable response.
+        let now = (new Date()).toString();
+        LOG("Received TIME request from " + aMessage.nickname +
+            ". Sending TIME response: \"" + now + "\".");
+        this.sendCTCPMessage("TIME", ":" + now, aMessage.nickname, true);
+      }
+      else {
+        // TIME :<human-readable-time-string>
+        // Received a TIME reply, display it.
+        // Remove the : prefix, if it exists and display the result.
+        let time = aMessage.ctcp.param.slice(aMessage.ctcp.param[0] == ":");
+        this.getConversation(aMessage.nickname)
+            .writeMessage(aMessage.nickname,
+                          _("ctcp.time", aMessage.nickname, time),
+                          {system: true});
+      }
+      return true;
+    },
+
+    // A string set by the user (never the client coder)
+    "USERINFO": function(aMessage) false,
+
+    // The version and type of the client.
+    "VERSION": function(aMessage) {
+      if (aMessage.command == "PRIVMSG") {
+        // VERSION
+        // Received VERSION request, send VERSION response.
+        // Use brandShortName as the client version.
+        let version =
+          l10nHelper("chrome://branding/locale/brand.properties")("brandShortName");
+        LOG("Received VERSION request from " + aMessage.nickname +
+            ". Sending VERSION response: \"" + version + "\".");
+        this.sendCTCPMessage("VERSION", version, aMessage.nickname, true);
+      }
+      else if (aMessage.command == "NOTICE" && aMessage.ctcp.param.length) {
+        // VERSION #:#:#
+        // Received VERSION response, display to the user.
+        let response = _("ctcp.version", aMessage.nickname,
+                         aMessage.ctcp.param);
+        this.getConversation(aMessage.nickname)
+            .writeMessage(aMessage.nickname, response, {system: true});
+      }
+      return true;
+    }
+  }
+};
new file mode 100644
--- /dev/null
+++ b/chat/protocols/irc/ircCommands.jsm
@@ -0,0 +1,319 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Instantbird.
+ *
+ * The Initial Developer of the Original Code is
+ * Patrick Cloke <clokep@gmail.com>.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// This is to be exported directly onto the IRC prplIProtocol object, directly
+// implementing the commands field before we register them.
+const EXPORTED_SYMBOLS = ["commands"];
+
+Components.utils.import("resource:///modules/ircUtils.jsm");
+
+// Shortcut to get the JavaScript account object.
+function getAccount(aConv) aConv.wrappedJSObject._account;
+
+// Kick a user from a channel
+// aMsg is <user> [comment]
+function kickCommand(aMsg, aConv) {
+  if (!aMsg.length)
+    return false;
+
+  let params = [aConv.name];
+  let offset = aMsg.indexOf(" ");
+  if (offset != -1) {
+    params.push(aMsg.slice(0, offset));
+    params.push(aMsg.slice(offset + 1));
+  }
+  else
+    params.push(aMsg);
+
+  getAccount(aConv).sendMessage("KICK", params);
+  return true;
+}
+
+// Send a message directly to a user.
+// aMsg is <user> <message>
+function messageCommand(aMsg, aConv) {
+  let sep = aMsg.indexOf(" ");
+  // If no space in the message or the first space is at the end of the message.
+  if (sep == -1 || (sep + 1) == aMsg.length) {
+    if (!aMsg.length || sep == 0)
+      return false;
+    getAccount(aConv).createConversation(aMsg);
+    return true;
+  }
+
+  return privateMessage(aConv, aMsg.slice(sep + 1), aMsg.slice(0, sep));
+}
+
+// aAdd is true to add a mode, false to remove a mode.
+function setMode(aNickname, aConv, aMode, aAdd) {
+  if (!aNickname.length)
+    return false;
+
+  // Change the mode for each nick, as separator by spaces.
+  return aNickname.split(" ").every(function(aNick)
+    simpleCommand(aConv, "MODE",
+                  [aConv.name, (aAdd ? "+" : "-") + aMode, aNick]));
+}
+
+function actionCommand(aMsg, aConv) {
+  if (!ctcpCommand(aConv, aConv.name, "ACTION", aMsg))
+    return false;
+
+  // Show the action on our conversation.
+  let account = getAccount(aConv);
+  account.getConversation(aConv.name)
+         .writeMessage(account._nickname, "/me " + aMsg, {outgoing: true});
+  return true;
+}
+
+// Helper functions
+function privateMessage(aConv, aMsg, aNickname) {
+  if (!aMsg.length)
+    return false;
+
+  // This will open the conversation, send and display the text
+  getAccount(aConv).getConversation(aNickname).sendMsg(aMsg);
+  return true;
+}
+
+// This will send a command to the server, if no parameters are given, it is
+// assumed that the command takes no parameters. aParams can be either a single
+// string or an array of parameters.
+function simpleCommand(aConv, aCommand, aParams) {
+  if (!aParams || !aParams.length)
+    getAccount(aConv).sendMessage(aCommand);
+  else
+    getAccount(aConv).sendMessage(aCommand, aParams);
+  return true;
+}
+
+function ctcpCommand(aConv, aTarget, aCommand, aMsg) {
+  if (!aTarget.length)
+    return false;
+
+  getAccount(aConv).sendCTCPMessage(aCommand, aMsg, aTarget, false);
+  return true;
+}
+
+var commands = [
+  {
+    name: "action",
+    get helpString() _("command.action", "action"),
+    run: actionCommand
+  },
+  {
+    name: "ctcp",
+    get helpString() _("command.ctcp", "ctcp"),
+    run: function(aMsg, aConv) {
+      let separator = aMsg.indexOf(" ");
+      if (separator == -1 && (separator + 1) != aMsg.length)
+        return false;
+
+      return ctcpCommand(aConv, aMsg.slice(0, separator),
+                         aMsg.slice(separator + 1));
+    }
+  },
+  {
+    name: "chanserv",
+    get helpString() _("command.chanserv", "chanserv"),
+    run: function(aMsg, aConv) privateMessage(aConv, aMsg, "ChanServ")
+  },
+  {
+    name: "deop",
+    get helpString() _("command.deop", "deop"),
+    run: function(aMsg, aConv) setMode(aMsg, aConv, "o", false)
+  },
+  {
+    name: "devoice",
+    get helpString() _("command.devoice", "devoice"),
+    run: function(aMsg, aConv) setMode(aMsg, aConv, "v", false)
+  },
+  {
+    name: "invite",
+    get helpString() _("command.invite", "invite"),
+    run: function(aMsg, aConv) simpleCommand(aConv, "INVITE", aMsg)
+  },
+  {
+    name: "j",
+    get helpString() _("command.join", "j"),
+    run: function(aMsg, aConv) simpleCommand(aConv, "JOIN", aMsg)
+  },
+  {
+    name: "join",
+    get helpString() _("command.join", "join"),
+    run: function(aMsg, aConv) simpleCommand(aConv, "JOIN", aMsg)
+  },
+  {
+    name: "kick",
+    get helpString() _("command.kick", "kick"),
+    run: kickCommand
+  },
+  {
+    name: "list",
+    get helpString() _("command.list", "list"),
+    run: function(aMsg, aConv) simpleCommand(aConv, "LIST")
+  },
+  {
+    name: "me",
+    get helpString() _("command.action", "me"),
+    run: actionCommand
+  },
+  {
+    name: "memoserv",
+    get helpString() _("command.memoserv", "memoserv"),
+    run: function(aMsg, aConv) privateMessage(aConv, aMsg, "MemoServ")
+  },
+  {
+    name: "mode",
+    get helpString() _("command.mode", "mode"),
+    run: function(aMsg, aConv) simpleCommand(aConv, "MODE", aMsg)
+  },
+  {
+    name: "msg",
+    get helpString() _("command.msg", "msg"),
+    run: messageCommand
+  },
+  {
+    name: "nick",
+    get helpString() _("command.nick", "nick"),
+    run: function(aMsg, aConv) simpleCommand(aConv, "NICK", aMsg)
+  },
+  {
+    name: "nickserv",
+    get helpString() _("command.nickserv", "nickserv"),
+    run: function(aMsg, aConv) privateMessage(aConv, aMsg, "NickServ")
+  },
+  {
+    name: "notice",
+    get helpString() _("command.notice", "notice"),
+    run: function(aMsg, aConv) simpleCommand(aConv, "NOTICE", aMsg)
+  },
+  {
+    name: "op",
+    get helpString() _("command.op", "op"),
+    run: function(aMsg, aConv) setMode(aMsg, aConv, "o", true)
+  },
+  {
+    name: "operwall",
+    get helpString() _("command.wallops", "operwall"),
+    run: function(aMsg, aConv) simpleCommand(aConv, "WALLOPS", aMsg)
+  },
+  {
+    name: "operserv",
+    get helpString() _("command.operserv", "operserv"),
+    run: function(aMsg, aConv) privateMessage(aConv, aMsg, "OperServ")
+  },
+  {
+    name: "part",
+    get helpString() _("command.part", "part"),
+    run: function (aMsg, aConv) {
+      aConv.wrappedJSObject.part(aMsg);
+      return true;
+    }
+  },
+  {
+    name: "ping",
+    get helpString() _("command.ping", "ping"),
+    run: function(aMsg, aConv) ctcpCommand(aConv, aMsg, "PING")
+  },
+  {
+    name: "query",
+    get helpString() _("command.msg", "query"),
+    run: messageCommand
+  },
+  {
+    name: "quit",
+    get helpString() _("command.quit", "quit"),
+    run: function(aMsg, aConv) {
+      getAccount(aConv).quit(aMsg);
+      return true;
+    }
+  },
+  {
+    name: "quote",
+    get helpString() _("command.quote", "quote"),
+    run: function(aMsg, aConv) {
+      if (!aMsg.length)
+        return false;
+
+      getAccount(aConv).sendRawMessage(aMsg);
+      return true;
+    }
+  },
+  {
+    name: "remove",
+    get helpString() _("command.kick", "remove"),
+    run: kickCommand
+  },
+  {
+    name: "time",
+    get helpString() _("command.time", "time"),
+    run: function(aMsg, aConv) simpleCommand(aConv, "TIME")
+  },
+  {
+    name: "topic",
+    get helpString() _("command.topic", "topic"),
+    run: function(aMsg, aConv) {
+      aConv.topic = aMsg;
+      return true;
+    }
+  },
+  {
+    name: "umode",
+    get helpString() _("command.umode", "umode"),
+    run: function(aMsg, aConv) simpleCommand(aConv, "MODE", aMsg)
+  },
+  {
+    name: "version",
+    get helpString() _("command.version", "version"),
+    run: function(aMsg, aConv) ctcpCommand(aConv, aMsg, "VERSION")
+  },
+  {
+    name: "voice",
+    get helpString() _("command.voice", "voice"),
+    run: function(aMsg, aConv) setMode(aMsg, aConv, "v", true)
+  },
+  {
+    name: "wallops",
+    get helpString() _("command.wallops", "wallops"),
+    run: function(aMsg, aConv) simpleCommand(aConv, "WALLOPS", aMsg)
+  },
+  {
+    name: "whowas",
+    get helpString() _("command.whowas", "whowas"),
+    run: function(aMsg, aConv) simpleCommand(aConv, "WHOWAS", aMsg)
+  }
+];
new file mode 100644
--- /dev/null
+++ b/chat/protocols/irc/ircDCC.jsm
@@ -0,0 +1,100 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Instantbird.
+ *
+ * The Initial Developer of the Original Code is
+ * Patrick Cloke <clokep@gmail.com>.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * This contains an implementation of the Direct Client-to-Client (DCC)
+ * protocol.
+ *   A description of the DCC protocol
+ *     http://www.irchelp.org/irchelp/rfc/dccspec.html
+ */
+
+const EXPORTED_SYMBOLS = ["ctcpDCC", "dccBase"];
+
+const Cu = Components.utils;
+
+Cu.import("resource:///modules/ircHandlers.jsm");
+Cu.import("resource:///modules/ircUtils.jsm");
+Cu.import("resource:///modules/jsProtoHelper.jsm");
+
+// Parse a CTCP message into a DCC message. A DCC message is a CTCP message of
+// the form:
+//   DCC <type> <argument> <address> <port> [<size>]
+function DCCMessage(aMessage) {
+  let message = aMessage;
+  let params = message.ctcp.param.split(" ");
+  if (params.length < 4) {
+    ERROR("Not enough DCC parameters:\n" + JSON.stringify(aMessage));
+    return null;
+  }
+
+  try {
+    // Address, port and size should be treated as unsigned long, unsigned short
+    // and unsigned long, respectively. The protocol is designed to handle
+    // further arguements, if necessary.
+    message.ctcp.dcc = {
+      type: params[0],
+      argument: params[1],
+      address: new Number(params[2]),
+      port: new Number(params[3]),
+      size: params.length == 5 ? new Number(params[4]) : null,
+      furtherArguments: params.length > 5 ? params.slice(5) : []
+    };
+  } catch (e) {
+    ERROR("Error parsing DCC parameters:\n" + JSON.stringify(aMessage));
+    return null;
+  }
+
+  return message;
+}
+
+// This is the DCC handler for CTCP, it will call each DCC handler.
+var ctcpDCC = {
+  name: "DCC",
+  // Slightly above default CTCP priority.
+  priority: ircHandlers.HIGH_PRIORITY + 10,
+
+  commands: {
+    // Handle a DCC message by parsing the message and executing any handlers.
+    "DCC": function(aMessage) {
+      // If there are no DCC handlers, then don't parse the DCC message.
+      if (!ircHandlers.hasDCCHandlers)
+        return false;
+
+      // Parse the message and attempt to handle it.
+      return ircHandlers.handleMessage(this, DCCMessage(aMessage));
+    }
+  }
+};
new file mode 100644
--- /dev/null
+++ b/chat/protocols/irc/ircHandlers.jsm
@@ -0,0 +1,153 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Instantbird.
+ *
+ * The Initial Developer of the Original Code is
+ * Patrick Cloke <clokep@gmail.com>.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const EXPORTED_SYMBOLS = ["ircHandlers"];
+
+const Cu = Components.utils;
+
+Cu.import("resource:///modules/imXPCOMUtils.jsm");
+Cu.import("resource:///modules/ircUtils.jsm");
+
+var ircHandlers = {
+  /*
+   * Object to hold the IRC handlers, each handler is an object that implements:
+   *   name        The display name of the handler.
+   *   priority    The priority of the handler (0 is default, positive is
+   *               higher priority)
+   *   commands    An object of commands, each command is a function which
+   *               accepts a message object and has 'this' bound to the account
+   *               object. It should return whether the message was successfully
+   *               handler or not.
+   */
+  _ircHandlers: [],
+  // Object to hold the ISUPPORT handlers, expects the same fields as
+  // _ircHandlers.
+  _isupportHandlers: [],
+  // Object to hold the CTCP handlers, expects the same fields as _ircHandlers.
+  _ctcpHandlers: [],
+  // Object to hold the DCC handlers, expects the same fields as _ircHandlers.
+  _dccHandlers: [],
+
+  _registerHandler: function(aArray, aHandler) {
+    // Protect ourselves from adding broken handlers.
+    if (!("commands" in aHandler)) {
+      ERROR("IRC handlers must have a \"commands\" property: " + aHandler.name);
+      return false;
+    }
+
+    aArray.push(aHandler);
+    aArray.sort(function(a, b) b.priority - a.priority);
+    return true;
+  },
+
+  _unregisterHandler: function(aArray, aHandler) {
+    aArray = aArray.filter(function(h) h.name != aHandler.name);
+  },
+
+  registerHandler: function(aHandler)
+    this._registerHandler(this._ircHandlers, aHandler),
+  unregisterHandler: function(aHandler)
+    this._unregisterHandler(this._ircHandlers, aHandler),
+
+  registerISUPPORTHandler: function(aHandler)
+    this._registerHandler(this._isupportHandlers, aHandler),
+  unregisterISUPPORTHandler: function(aHandler)
+    this._unregisterHandler(this._isupportHandlers, aHandler),
+
+  registerCTCPHandler: function(aHandler)
+    this._registerHandler(this._ctcpHandlers, aHandler),
+  unregisterCTCPHandler: function(aHandler)
+    this._unregisterHandler(this._ctcpHandlers, aHandler),
+
+  registerDCCHandler: function(aHandler)
+    this._registerHandler(this._dccHandlers, aHandler),
+  unregisterDCCHandler: function(aHandler)
+    this._unregisterHandler(this._dccHandlers, aHandler),
+
+  // Handle a message based on a set of handlers.
+  _handleMessage: function(aHandlers, aAccount, aMessage, aCommand) {
+    // Loop over each handler and run the command until one handles the message.
+    for each (let handler in aHandlers) {
+      try {
+        // Attempt to execute the command, by checking if the handler has the
+        // command.
+        // Parse the command with the JavaScript account object as "this".
+        if (hasOwnProperty(handler.commands, aCommand) &&
+            handler.commands[aCommand].call(aAccount, aMessage)) {
+          DEBUG(JSON.stringify(aMessage));
+          return true;
+        }
+      } catch (e) {
+        // We want to catch an error here because one of our handlers are broken,
+        // if we don't catch the error, the whole IRC plug-in will die.
+        ERROR("Error running command " + aCommand + " with handler " +
+              handler.name + ":\n" + JSON.stringify(aMessage));
+        Cu.reportError(e);
+      }
+    }
+
+    return false;
+  },
+
+  handleMessage: function(aAccount, aMessage)
+    this._handleMessage(this._ircHandlers, aAccount, aMessage,
+                        aMessage.command.toUpperCase()),
+
+  handleISUPPORTMessage: function(aAccount, aMessage)
+    this._handleMessage(this._isupportHandlers, aAccount, aMessage,
+                        aMessage.isupport.parameter),
+
+  // aMessage is a CTCP Message, which inherits from an IRC Message.
+  handleCTCPMessage: function(aAccount, aMessage)
+    this._handleMessage(this._ctcpHandlers, aAccount, aMessage,
+                        aMessage.ctcp.command),
+
+  // aMessage is a DCC Message, which inherits from a CTCP Message.
+  handleDCCPMessage: function(aAccount, aMessage)
+    this._handleMessage(this._dccHandlers, aAccount, aMessage,
+                        aMessage.ctcp.dcc.type),
+
+  // Checking if handlers exist.
+  get hasHandlers() this._ircHandlers.length > 0,
+  get hasISUPPORTHandlers() this._isupportHandlers.length > 0,
+  get hasCTCPHandlers() this._ctcpHandlers.length > 0,
+  get hasDCCHandlers() this._dccHandlers.length > 0,
+
+  // Some constant priorities.
+  get LOW_PRIORITY() -100,
+  get DEFAULT_PRIORITY() 0,
+  get HIGH_PRIORITY() 100
+}
new file mode 100644
--- /dev/null
+++ b/chat/protocols/irc/ircISUPPORT.jsm
@@ -0,0 +1,255 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Instantbird.
+ *
+ * The Initial Developer of the Original Code is
+ * Patrick Cloke <clokep@gmail.com>.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * This implements the ISUPPORT parameters for the 005 numeric to allow a server
+ * to notify a client of what capabilities it supports.
+ *   The 005 numeric
+ *     http://www.irc.org/tech_docs/005.html
+ *   RFC Drafts: IRC RPL_ISUPPORT Numeric Definition
+ *     http://tools.ietf.org/html/draft-brocklesby-irc-isupport-03
+ *     http://tools.ietf.org/html/draft-hardy-irc-isupport-00
+ */
+
+const EXPORTED_SYMBOLS = ["ircISUPPORT"];
+
+const Cu = Components.utils;
+
+Cu.import("resource:///modules/ircHandlers.jsm");
+Cu.import("resource:///modules/ircUtils.jsm");
+
+function isupportMessage(aMessage, aToken) {
+  let message = aMessage;
+  message.isupport = {};
+  message.isupport.useDefault = aToken[0] == "-";
+
+  let token = message.isupport.useDefault ? aToken.slice(1) : aToken;
+  [message.isupport.parameter, message.isupport.value] = token.split("=");
+
+  return message;
+}
+
+var ircISUPPORT = {
+  name: "ISUPPORT",
+  // Slightly above default RFC 2812 priority.
+  priority: ircHandlers.DEFAULT_PRIORITY + 10,
+
+  commands: {
+    // RPL_ISUPPORT
+    // [-]<parameter>[=<value>] :are supported by this server
+    "005": function(aMessage) {
+      if (!("ISUPPORT" in this))
+        this.ISUPPORT = {};
+
+      // Seperate the ISUPPORT parameters.
+      let tokens = aMessage.params[1].split(" ");
+
+      let handled = true;
+      for (let token in tokens) {
+        let message = isupportMessage(aMessage, token);
+        handled &= ircHandlers.handleISUPPORTMessage(this, message);
+      }
+
+      return handled;
+    }
+  }
+}
+
+function setSimpleNumber(aAccount, aField, aMessage, aDefaultValue) {
+  let value =
+    aMessage.isupport.value ? new Number(aMessage.isupport.value) : null;
+  aAccount[aField] = (value && !isNaN(value)) ? value : aDefaultValue;
+  return true;
+}
+
+// Generates a function that will set the ASCII range of aStart-aEnd as the
+// uppercase of (aStart-aEnd) + 0x20.
+function generateNormalize(aStart, aEnd) {
+  const exp = new RegExp("[\\x" + aStart.toString(16) + "-\\x" +
+                         aEnd.toString(16) + "]", "g");
+  return function(aStr, aRemoveStatus) {
+    let str = aStr;
+    if (aPrefixes && aPrefixes.indexOf(aStr[0]) != -1)
+      str = str.slice(1);
+      return str.replace(exp,
+                         function(c) String.fromCharCode(c.charCodeAt(0) + 0x20));
+  };
+}
+
+var isupportBase = {
+  name: "ISUPPORT",
+  description: "IRC RPL_ISUPPORT Numeric Definition",
+  priority: ircHandlers.DEFAULT_PRIORITY,
+
+  commands: {
+    "CASEMAPPING": function(aMessage) {
+      // CASEMAPPING=<mapping>
+      // Allows the server to specify which method it uses to compare equality
+      // of case-insensitive strings.
+
+      // By default, use rfc1459 type case mapping.
+      let value = aMessage.isupport.useDefault ?
+        "rfc1493" : aMessage.isupport.value;
+
+      // Set the normalize function of the account to use the proper case
+      // mapping.
+      if (value == "ascii") {
+        // The ASCII characters 97 to 122 (decimal) are the lower-case
+        // characters of ASCII 65 to 90 (decimal).
+        this.normalize = generateNormalize(65, 90);
+      }
+      else if (value == "rfc1493") {
+        // The ASCII characters 97 to 126 (decimal) are the lower-case
+        // characters of ASCII 65 to 94 (decimal).
+        this.normalize = generateNormalize(65, 94);
+      }
+      else if (value == "strict-rfc1459") {
+        // The ASCII characters 97 to 125 (decimal) are the lower-case
+        // characters of ASCII 65 to 93 (decimal).
+        this.normalize = generateNormalize(65, 93);
+      }
+      return true;
+    },
+    "CHANLIMIT": function(aMessage) {
+      // CHANLIMIT=<prefix>:<number>[,<prefix>:<number>]*
+      // Note that each <prefix> can actually contain multiple prefixes, this
+      // means the sum of those prefixes is given.
+      this.maxChannels = {};
+
+      let pairs = aMessage.isupport.value.split(",");
+      for each (let pair in pairs) {
+        let [prefixes, num] = pair.split(":");
+        this.maxChannels[prefix] = num;
+      }
+      return true;
+    },
+    "CHANMODES": function(aMessage) false,
+    "CHANNELLEN": function(aMessage) {
+      // CHANNELLEN=<number>
+      // Default is from RFC 1493.
+      return setSimpleNumber(this, "maxChannelLength", aMessage, 200);
+    },
+    "CHANTYPES": function(aMessage) {
+      // CHANTYPES=[<channel prefix>]*
+      let value = aMessage.isupport.useDefault ? "#&" : aMessage.isupport.value;
+      this.channelPrefixes = value.split("");
+      return true;
+    },
+    "EXCEPTS": function(aMessage) false,
+    "IDCHAN": function(aMessage) false,
+    "INVEX": function(aMessage) false,
+    "KICKLEN": function(aMessage) {
+      // KICKLEN=<number>
+      // Default value is Infinity.
+      return setSimpleNumber(this, "maxKickLength", aMessage, Infinity);
+    },
+    "MAXLIST": function(aMessage) false,
+    "MODES": function(aMessage) false,
+    "NETWORK": function(aMessage) false,
+    "NICKLEN": function(aMessage) {
+      // NICKLEN=<number>
+      // Default value is from RFC 1493.
+      return setSimpleNumber(this, "maxNicknameLength", aMessage, 9);
+    },
+    "PREFIX": function(aMessage) {
+      // PREFIX=[(<mode character>*)<prefix>*]
+      let value =
+        aMessage.isupport.useDefault ? "(ov)@+" : aMessage.isupport.value;
+
+      this.userPrefixToModeMap = {};
+      // A null value specifier indicates that no prefixes are supported.
+      if (!value.length)
+        return true;
+
+      let matches = /\(([a-z]*)\)(.*)/i.exec(value);
+      if (!matches) {
+        // The pattern doesn't match.
+        WARN("Invalid PREFIX value: " + value);
+        return false;
+      }
+      if (matches[1].length != matches[2].length) {
+        WARN("Invalid PREFIX value, does not provide one-to-one mapping:" +
+             value);
+        return false;
+      }
+
+      for (let i = 0; i < matches[2].length; i++)
+        this.userPrefixToModeMap[matches[2][i]] = matches[1][i];
+      return true;
+    },
+    "SAFELIST": function(aMessage) false,
+    "STATUSMSG": function(aMessage) false,
+    "STD": function(aMessage) {
+      // This was never updated as the RFC was never formalized.
+      if (aMessage.isupport.value != "rfcnnnn")
+        WARN("Unknown ISUPPORT numeric form: " + aMessage.isupport.value);
+      return true;
+    },
+    "TARGMAX": function(aMessage) {
+      // TARGMAX=<command>:<max targets>[,<command>:<max targets>]*
+      if (aMessage.isupport.useDefault) {
+        this.maxTargets = 1;
+        return true;
+      }
+
+      this.maxTargets = {};
+      let commands = aMessage.isupport.value.split(",");
+      for (let i = 0; i < commands.length; i++) {
+        let [command, limitStr] = commands[i].split("=");
+        let limit = limitStr ? new Number(limit) : Infinity;
+        if (isNaN(limit)) {
+          WARN("Invalid maximum number of targets: " + limitStr);
+          continue;
+        }
+        this.maxTargets[command] = limit;
+      }
+      return true;
+    },
+    "TOPICLEN": function(aMessage) {
+      // TOPICLEN=<number>
+      // Default value is Infinity.
+      return setSimpleNumber(this, "maxTopicLength", aMessage, Infinity);
+    },
+
+    // The following are considered "obsolete" by the RFC, but are still in use.
+    "CHARSET": function(aMessage) false,
+    "MAXBANS": function(aMessage) false,
+    "MAXCHANNELS": function(aMessage) false,
+    "MAXTARGETS": function(aMessage) {
+      return setSimpleNumber(this, "maxTargets", aMessage, 1);
+    }
+  }
+};
new file mode 100644
--- /dev/null
+++ b/chat/protocols/irc/ircUtils.jsm
@@ -0,0 +1,207 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Instantbird.
+ *
+ * The Initial Developer of the Original Code is
+ * Patrick Cloke <clokep@gmail.com>.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Mark "Mook" Yen <Mook.moz+Instantbird.code@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const EXPORTED_SYMBOLS = ["DEBUG", "LOG", "WARN", "ERROR", "_",
+                          "ctcpFormatToText", "ctcpFormatToHTML"];
+
+const {classes: Cc, interfaces: Ci} = Components;
+
+Components.utils.import("resource:///modules/imXPCOMUtils.jsm");
+initLogModule("irc", this);
+
+XPCOMUtils.defineLazyGetter(this, "_", function()
+  l10nHelper("chrome://chat/locale/irc.properties")
+);
+
+XPCOMUtils.defineLazyGetter(this, "TXTToHTML", function() {
+  let cs = Cc["@mozilla.org/txttohtmlconv;1"].getService(Ci.mozITXTToHTMLConv);
+  return function(aTXT) cs.scanTXT(aTXT, cs.kEntities);
+});
+
+// The supported formatting control characters, as described as deprecated in
+// http://www.invlogic.com/irc/ctcp.html#3.11
+const CTCP_TAGS = {"\x02": "b", // \002, ^B, Bold
+                   "\x16": "i", // \026, ^V, Reverse or Inverse (Italics)
+                   "\x1F": "u", // \037, ^_, Underline
+                   "\x03": mIRCColoring, // \003, ^C, Coloring
+                   "\x0F": null}; // \017, ^O, Clear all formatting
+
+// Generate an expression that will search for any of the control characters.
+const CTCP_TAGS_STRING = "[" + Object.keys(CTCP_TAGS).join("") + "]";
+const CTCP_TAGS_EXP = new RegExp(CTCP_TAGS_STRING);
+const CTCP_TAGS_EXP_GLOBAL = new RegExp(CTCP_TAGS_STRING, "g");
+
+// Remove all CTCP formatting characters.
+function ctcpFormatToText(aString) aString.replace(CTCP_TAGS_EXP_GLOBAL, "")
+
+// Close the tags in the opposite order they were opened.
+function closeStack(aStack)
+  aStack.reverse().map(function(aTag) "</" + aTag.split(" ", 1) + ">").join("")
+
+/**
+ * Convert a string from CTCP escaped formatting to HTML markup.
+ * @param aString the string with CTCP formatting to parse
+ * @return The HTML output string
+ */
+function ctcpFormatToHTML(aString) {
+  let next,
+      stack = [],
+      input = TXTToHTML(aString),
+      output = "",
+      length;
+
+  while ((next = CTCP_TAGS_EXP.exec(input))) {
+    if (next.index > 0)
+      output += input.substr(0, next.index);
+    length = 1;
+    let tag = CTCP_TAGS[input[next.index]];
+    if (tag === null) {
+      // Clear all formatting.
+      output += closeStack(stack);
+      stack = [];
+    }
+    else if (typeof tag == "function") {
+      [stack, output, length] = tag(stack, input.substr(next.index), output);
+    }
+    else {
+      let offset = stack.indexOf(tag);
+      if (offset == -1) {
+        // Tag not found; open new tag.
+        output += "<" + tag + ">";
+        stack.push(tag);
+      }
+      else {
+        // Tag found; close existing tag (and all tags after it).
+        output += closeStack(stack.slice(offset));
+        // Reopen the tags that came after it.
+        stack.slice(offset + 1)
+             .forEach(function(aTag) output += "<" + aTag + ">");
+        // Remove the tag from the stack.
+        stack.splice(offset, 1);
+      }
+    }
+
+    // Avoid infinite loops, if.
+    length = (length <= 0) ? 1 : length;
+    // Skip to after the last match.
+    input = input.substr(next.index + length);
+  }
+  // Return unmatched bits and close any open tags at the end.
+  return output + input + closeStack(stack);
+}
+
+// mIRC colors are defined at http://www.mirc.com/colors.html.
+// This expression matches \003<one or two digits>[,<one or two digits>].
+const M_IRC_COLORS_EXP = /^\x03(?:(\d\d?)(?:,(\d\d?))?)?/;
+const M_IRC_COLOR_MAP = {
+  "0": "white",
+  "1": "black",
+  "2": "navy", // blue (navy)
+  "3": "green",
+  "4": "red",
+  "5": "maroon", // brown (maroon)
+  "6": "purple",
+  "7": "orange", // orange (olive)
+  "8": "yellow",
+  "9": "lime", // light green (lime)
+  "10": "teal", // teal (a green/blue cyan)
+  "11": "aqua", // light cyan (cyan) (aqua)
+  "12": "blue", // light blue (royal)",
+  "13": "fuchsia", // pink (light purple) (fuchsia)
+  "14": "grey",
+  "15": "silver", // light grey (silver)
+  "99": "transparent"
+};
+
+function mIRCColoring(aStack, aInput, aOutput) {
+  function getColor(aKey) {
+    let key = aKey;
+    // Single digit numbers can (must?) be prefixed by a zero.
+    if (key.length == 2 && key[0] == "0")
+      key = key[1];
+
+    if (M_IRC_COLOR_MAP.hasOwnProperty(key))
+      return M_IRC_COLOR_MAP[key];
+
+    return null;
+  }
+
+  let matches,
+      stack = aStack,
+      input = aInput,
+      output = aOutput,
+      length = 1;
+
+  if ((matches = M_IRC_COLORS_EXP.exec(input))) {
+    let format = ["font"];
+
+    if (!matches[1]) {
+      // Find the first font tag.
+      let offset = stack.map(function(aTag) aTag.indexOf("font") == 0)
+                        .indexOf(true);
+
+      // Close all tags after the first font tag.
+      output += closeStack(stack.slice(offset));
+      // Remove the font tags from the stack.
+      stack = stack.filter(function(aTag) aTag.indexOf("font"));
+      // Reopen the other tags.
+      stack.slice(offset)
+           .forEach(function(aTag) output += "<" + aTag + ">");
+    }
+    else {
+      // The foreground color.
+      let color = getColor(matches[1]);
+      if (color)
+        format.push("color=\"" + color + "\"");
+
+      // The background color.
+      if (matches[2]) {
+        let color = getColor(matches[2]);
+        if (color)
+          format.push("background=\"" + color + "\"");
+      }
+
+      if (format.length > 1) {
+        output += "<" + format.join(" ") + ">";
+        stack.push(format.join(" "));
+        length = matches[0].length;
+      }
+    }
+  }
+
+  return [stack, output, length];
+}
new file mode 100644
--- /dev/null
+++ b/chat/protocols/irc/jar.mn
@@ -0,0 +1,5 @@
+chat.jar:
+% skin prpl-irc classic/1.0 %skin/classic/prpl/irc/
+	skin/classic/prpl/irc/icon32.png	(icons/prpl-irc-32.png)
+	skin/classic/prpl/irc/icon48.png	(icons/prpl-irc-48.png)
+	skin/classic/prpl/irc/icon.png	(icons/prpl-irc.png)
new file mode 100644
--- /dev/null
+++ b/chat/protocols/irc/test/test_ctcpColoring.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Components.utils.import("resource:///modules/ircUtils.jsm");
+
+const input = [
+  // From http://www.mirc.com/colors.html
+  "\x035,12colored text and background\x03",
+  "\x035colored text\x03",
+  "\x033colored text \x035,2more colored text and background\x03",
+  "\x033,5colored text and background \x038other colored text but same background\x03",
+  "\x033,5colored text and background \x038,7other colored text and different background\x03",
+
+  // Based on above, but more complicated.
+  "\x02\x035,12colored \x1Ftext and background\x03. You sure about this?",
+
+  // Implied by above.
+  "So a \x03,8 attribute is not valid and thus ignored.",
+
+  // Try some of the above with two digits.
+  "\x0303,5colored text and background \x0308other colored text but same background\x03",
+  "\x0303,05colored text and background \x038,7other colored text and different background\x03",
+];
+
+function run_test() {
+  add_test(test_mIRCColoring);
+  //add_test(test_ctcpFormatToText);
+
+  run_next_test();
+}
+
+function test_mIRCColoring() {
+  let expectedOutput = [
+    "<font color=\"maroon\" background=\"blue\">colored text and background</font>",
+    "<font color=\"maroon\">colored text</font>",
+    "<font color=\"green\">colored text <font color=\"maroon\" background=\"navy\">more colored text and background</font></font>",
+    "<font color=\"green\" background=\"maroon\">colored text and background <font color=\"yellow\">other colored text but same background</font></font>",
+    "<font color=\"green\" background=\"maroon\">colored text and background <font color=\"yellow\" background=\"orange\">other colored text and different background</font></font>",
+    "<b><font color=\"maroon\" background=\"blue\">colored <u>text and background</u></font><u>. You sure about this?</u></b>",
+    "So a ,8 attribute is not valid and thus ignored.",
+    "<font color=\"green\" background=\"maroon\">colored text and background <font color=\"yellow\">other colored text but same background</font></font>",
+    "<font color=\"green\" background=\"maroon\">colored text and background <font color=\"yellow\" background=\"orange\">other colored text and different background</font></font>"
+  ];
+
+  let output = input.map(ctcpFormatToHTML);
+  for (let i = 0; i < output.length; i++)
+    do_check_eq(expectedOutput[i], output[i]);
+
+  run_next_test();
+}
+
+function test_ctcpFormatToText() {
+  let expectedOutput = "The quick brown fox jumps over the lazy dog.";
+
+  let output = input.map(ctcpFormatToText);
+  for (let i = 0; i < output.length; i++)
+    do_check_eq(expectedOutput, output[i]);
+
+  run_next_test();
+}
+
+function test_mIRCColor() {
+  let expectedOutput = "The quick brown fox jumps over the lazy dog.";
+
+  let output = input.map(ctcpFormatToText);
+  for (let i = 0; i < output.length; i++)
+    do_check_eq(expectedOutput, output[i]);
+
+  run_next_test();
+}
new file mode 100644
--- /dev/null
+++ b/chat/protocols/irc/test/test_ctcpFormatting.js
@@ -0,0 +1,57 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Components.utils.import("resource:///modules/ircUtils.jsm");
+
+//TODO add a test for special JS characters (|, etc...)
+
+const input = [
+  "The quick brown fox \x02jumps\x02 over the lazy dog.",
+  "The quick brown fox \x02jumps\x0F over the lazy dog.",
+  "The quick brown \x16fox jumps\x16 over the lazy dog.",
+  "The quick brown \x16fox jumps\x0F over the lazy dog.",
+  "The quick \x1Fbrown fox jumps over the lazy\x1F dog.",
+  "The quick \x1Fbrown fox jumps over the lazy\x0F dog.",
+  "The quick \x1Fbrown fox \x02jumps over the lazy\x1F dog.",
+  "The quick \x1Fbrown fox \x02jumps\x1F over the lazy\x02 dog.",
+  "The quick \x1Fbrown \x16fox \x02jumps\x1F over\x16 the lazy\x02 dog.",
+  "The quick \x1Fbrown \x16fox \x02jumps\x0F over \x16the lazy \x02dog."
+];
+
+function run_test() {
+  add_test(test_ctcpFormatToHTML);
+  add_test(test_ctcpFormatToText);
+
+  run_next_test();
+}
+
+function test_ctcpFormatToHTML() {
+  let expectedOutput = [
+    "The quick brown fox <b>jumps</b> over the lazy dog.",
+    "The quick brown fox <b>jumps</b> over the lazy dog.",
+    "The quick brown <i>fox jumps</i> over the lazy dog.",
+    "The quick brown <i>fox jumps</i> over the lazy dog.",
+    "The quick <u>brown fox jumps over the lazy</u> dog.",
+    "The quick <u>brown fox jumps over the lazy</u> dog.",
+    "The quick <u>brown fox <b>jumps over the lazy</b></u><b> dog.</b>",
+    "The quick <u>brown fox <b>jumps</b></u><b> over the lazy</b> dog.",
+    "The quick <u>brown <i>fox <b>jumps</b></i></u><i><b> over</b></i><b> the lazy</b> dog.",
+    "The quick <u>brown <i>fox <b>jumps</b></i></u> over <i>the lazy <b>dog.</b></i>"
+  ];
+
+  let output = input.map(ctcpFormatToHTML);
+  for (let i = 0; i < output.length; i++)
+    do_check_eq(expectedOutput[i], output[i]);
+
+  run_next_test();
+}
+
+function test_ctcpFormatToText() {
+  let expectedOutput = "The quick brown fox jumps over the lazy dog.";
+
+  let output = input.map(ctcpFormatToText);
+  for (let i = 0; i < output.length; i++)
+    do_check_eq(expectedOutput, output[i]);
+
+  run_next_test();
+}
new file mode 100644
--- /dev/null
+++ b/chat/protocols/irc/test/xpcshell.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+head =
+tail =
+
+[test_ctcpFormatting.js]
+[test_ctcpColoring.js]
new file mode 100644
--- /dev/null
+++ b/chat/protocols/jsTest/Makefile.in
@@ -0,0 +1,48 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is
+# Netscape Communications Corporation.
+# Portions created by the Initial Developer are Copyright (C) 1998
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either of the GNU General Public License Version 2 or later (the "GPL"),
+# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH		= ../../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+ifdef MOZ_DEBUG
+EXTRA_COMPONENTS += jsTestProtocol.js jsTestProtocol.manifest
+endif
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/chat/protocols/jsTest/jsTestProtocol.js
@@ -0,0 +1,139 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2010.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+Components.utils.import("resource:///modules/imXPCOMUtils.jsm");
+Components.utils.import("resource:///modules/jsProtoHelper.jsm");
+
+function Conversation(aAccount)
+{
+  this._init(aAccount);
+}
+Conversation.prototype = {
+  _disconnected: false,
+  _setDisconnected: function() {
+    this._disconnected = true;
+  },
+  close: function() {
+    if (!this._disconnected)
+      this.account.disconnect(true);
+  },
+  sendMsg: function (aMsg) {
+    if (this._disconnected) {
+      this.writeMessage("jstest", "This message could not be sent because the conversation is no longer active: " + aMsg, {system: true, error: true});
+      return;
+    }
+
+    this.writeMessage("You", aMsg, {outgoing: true});
+    this.writeMessage("/dev/null", "Thanks! I appreciate your attention.",
+                      {incoming: true, autoResponse: true});
+  },
+
+  get name() "/dev/null",
+};
+Conversation.prototype.__proto__ = GenericConvIMPrototype;
+
+function Account(aProtoInstance, aImAccount)
+{
+  this._init(aProtoInstance, aImAccount);
+}
+Account.prototype = {
+  connect: function() {
+    this.reportConnecting();
+    // do something here
+    this.reportConnected();
+    setTimeout((function() {
+      this._conv = new Conversation(this);
+      this._conv.writeMessage("jstest", "You are now talking to /dev/null", {system: true});
+    }).bind(this), 0);
+  },
+  _conv: null,
+  disconnect: function(aSilent) {
+    this.reportDisconnecting(Components.interfaces.prplIAccount.NO_ERROR, "");
+    if (!aSilent)
+      this._conv.writeMessage("jstest", "You have disconnected.", {system: true});
+    if (this._conv) {
+      this._conv._setDisconnected();
+      delete this._conv;
+    }
+    this.reportDisconnected();
+  },
+
+  get canJoinChat() true,
+  chatRoomFields: {
+    channel: {label: "_Channel Field", required: true},
+    channelDefault: {label: "_Field with default", default: "Default Value"},
+    password: {label: "_Password Field", default: "", isPassword: true,
+               required: false},
+    sampleIntField: {label: "_Int Field", default: 4, min: 0, max: 10,
+                     required: true}
+  }
+};
+Account.prototype.__proto__ = GenericAccountPrototype;
+
+function jsTestProtocol() { }
+jsTestProtocol.prototype = {
+  get name() "JS Test",
+  options: {
+    "text": {label: "Text option",    default: "foo"},
+    "bool": {label: "Boolean option", default: true},
+    "int" : {label: "Integer option", default: 42},
+    "list": {label: "Select option",  default: "option2",
+             listValues: {"option1": "First option",
+                          "option2": "Default option",
+                          "option3": "Other option"}}
+  },
+  usernameSplits: [
+    {label: "Server", separator: "@", defaultValue: "default.server",
+     reverse: true}
+  ],
+  getAccount: function(aImAccount) new Account(this, aImAccount),
+  classID: Components.ID("{a0774c5a-4aea-458b-9fbc-8d3cbf1a4630}"),
+};
+jsTestProtocol.prototype.__proto__ = GenericProtocolPrototype;
+
+function overrideTestProtocol() { }
+overrideTestProtocol.prototype = {
+  get normalizedName() "override",
+  get name() "Override Test",
+  get iconBaseURI() "chrome://prpl-qq/skin/",
+  get baseId() "prpl-null",
+  classID: Components.ID("{88795348-8a4b-4018-890d-5314cb08ec4d}")
+};
+overrideTestProtocol.prototype.__proto__ = ForwardProtocolPrototype;
+
+const NSGetFactory = XPCOMUtils.generateNSGetFactory([jsTestProtocol,
+                                                      overrideTestProtocol]);
new file mode 100644
--- /dev/null
+++ b/chat/protocols/jsTest/jsTestProtocol.manifest
@@ -0,0 +1,6 @@
+component {a0774c5a-4aea-458b-9fbc-8d3cbf1a4630} jsTestProtocol.js
+contract @mozilla.org/chat/jstest;1 {a0774c5a-4aea-458b-9fbc-8d3cbf1a4630}
+category im-protocol-plugin prpl-jstest @mozilla.org/chat/jstest;1
+component {88795348-8a4b-4018-890d-5314cb08ec4d} jsTestProtocol.js
+contract @mozilla.org/chat/override;1 {88795348-8a4b-4018-890d-5314cb08ec4d}
+category im-protocol-plugin prpl-override @mozilla.org/chat/override;1
new file mode 100644
--- /dev/null
+++ b/chat/protocols/twitter/Makefile.in
@@ -0,0 +1,49 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is
+# Netscape Communications Corporation.
+# Portions created by the Initial Developer are Copyright (C) 1998
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either of the GNU General Public License Version 2 or later (the "GPL"),
+# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH		= ../../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+EXTRA_COMPONENTS = \
+		twitter.js \
+		twitter.manifest \
+		$(NULL)
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..207178f2bd66056a1e34116257f2b3cf574116a1
GIT binary patch
literal 1718
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
M07*qoM6N<$g5EzaZ~y=R
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a906034a29872b8ab8a62d3fc5dcfdcaf0c84eb0
GIT binary patch
literal 2598
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
IM6N<$f`S{uG5`Po
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5d3df06ea3960bc71be37376068c9f60296969a4
GIT binary patch
literal 789
zc$@(h1M2*VP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10+LBYK~y-6eUZIymSq&if9JmM=i`0)L2IS0R9XiNA!^Ws1Q#5P
zqlq!ZU5qZ8{v8YqBn&bUr;db;fyqrl9R;ZjLb0~>X?fq4=Y2lzFNe3hU_!W(Go0af
zopYTdt2h75o=i8Zh0$LpA}0k<fQTS~2nd1_4tv1{|DdwrTGmf5n{ul9?ubq=El=bu
z7dg3BV;C5ljBKC_fz4riVzX{9?#;H*7|d5oMaFAs!lSC;^W}Mt<_SNQ18zPp5kS4L
z&2(aTd#WH189IMKiNtr7ik!QHr<`AzXK}K?>x55^z0R>B$477>;$p;wNWXL3->+Eg
zWhjx8#)U|xg}v6XkZ0_;hyVfMi<NnP{&&Dr=h<y6+f9p$ktdxaRSF*?W_^FiN7hq&
zPojjqHjJi5#X?c=*-C*CNENIPDz4mnMC}86t>sQRz*NpNu#UU^3ZsO7T8~HA?mU}X
zn9zbDG7OP3(-Z7AmV5gZ@6AqfU_B-`nnWpbqcNiByNHbufYOdW5dkCcegP=qe@stn
z5)pb@(}h6gBR0ZYnd0n}8Nc#~5pMlmvQ>Ay$e7${j2^i)eq`VxBqF?#DxT^0LB-l%
z+vG+AjG2+a;W}LibOGuRS+5;U84y4V%$k&IAD-fyJAaI_zbr!8THY-TKMpLFhlh=S
zegt4?smBMiQ~a`fFlJP4G!9tkWnAAV`FJMb+;oDD5W}c!gvC^GG*Rq!j)h)^gTn|~
z-`=Y2+0!$V+&>s{b$yfb$LIKTu0V^7Z*X8eH#T<g5l+ucvQ=Aht#8ZfZx6l{Q9m3n
zQr_%kWcoSuGPOSPxNg}PcElJim^Sv;X7rrwwC>xT#+`X3{Y=8IxsvbuuD|vlI1eb>
T^vKSy00000NkvXXu0mjf!kuNR
new file mode 100644
--- /dev/null
+++ b/chat/protocols/twitter/jar.mn
@@ -0,0 +1,5 @@
+chat.jar:
+% skin prpl-twitter classic/1.0 %skin/classic/prpl/twitter/
+	skin/classic/prpl/twitter/icon32.png	(icons/prpl-twitter-32.png)
+	skin/classic/prpl/twitter/icon48.png	(icons/prpl-twitter-48.png)
+	skin/classic/prpl/twitter/icon.png	(icons/prpl-twitter.png)
new file mode 100644
--- /dev/null
+++ b/chat/protocols/twitter/twitter.js
@@ -0,0 +1,1022 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2010.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Cloke <clokep@instantbird.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
+
+Cu.import("resource:///modules/http.jsm");
+Cu.import("resource:///modules/imServices.jsm");
+Cu.import("resource:///modules/imXPCOMUtils.jsm");
+Cu.import("resource:///modules/jsProtoHelper.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "_", function()
+  l10nHelper("chrome://chat/locale/twitter.properties")
+);
+XPCOMUtils.defineLazyGetter(this, "_lang", function()
+  l10nHelper("chrome://global/locale/languageNames.properties")
+);
+initLogModule("twitter", this);
+
+function ChatBuddy(aName) {
+  this._name = aName;
+}
+ChatBuddy.prototype = GenericConvChatBuddyPrototype;
+
+function Tweet(aTweet, aWho, aMessage, aObject)
+{
+  this._tweet = aTweet;
+  this._init(aWho, aMessage, aObject);
+}
+Tweet.prototype = {
+  __proto__: GenericMessagePrototype,
+  _deleted: false,
+  getActions: function(aCount) {
+    let account = this.conversation._account;
+    if (!account.connected) {
+      if (aCount)
+        aCount.value = 0;
+      return [];
+    }
+
+    let actions = [
+      new Action(_("action.reply"), function() {
+        this.conversation.startReply(this._tweet);
+      }, this)
+    ];
+    if (this.incoming) {
+      actions.push(
+        new Action(_("action.retweet"), function() {
+          this.conversation.reTweet(this._tweet);
+        }, this)
+      );
+      let friend = account.isFriend(this._tweet.user);
+      if (friend !== null) {
+        let action = friend ? "stopFollowing" : "follow";
+        let screenName = this._tweet.user.screen_name;
+        actions.push(new Action(_("action." + action, screenName),
+                                function() { account[action](screenName); }));
+      }
+    }
+    else if (this.outgoing && !this._deleted) {
+      actions.push(
+        new Action(_("action.delete"), function() {
+          this.destroy();
+        }, this)
+      );
+    }
+    actions.push(new Action(_("action.copyLink"), function() {
+      let href = "https://twitter.com/#!/" + this._tweet.user.screen_name +
+                 "/status/" + this._tweet.id_str;
+      Cc["@mozilla.org/widget/clipboardhelper;1"]
+        .getService(Ci.nsIClipboardHelper).copyString(href);
+    }, this));
+    if (aCount)
+      aCount.value = actions.length;
+    return actions;
+  },
+  destroy: function() {
+    // Mark the tweet as deleted until we receive a response.
+    this._deleted = true;
+
+    this.conversation._account.destroy(this._tweet, this.onDestroyCallback,
+                                       this.onDestroyErrorCallback, this);
+  },
+  onDestroyErrorCallback: function(aException, aData) {
+    // The tweet was not successfully deleted.
+    delete this._deleted;
+    let error = this.conversation._parseError(aData);
+    this.conversation.systemMessage(_("error.delete", error,
+                                      this.originalMessage), true);
+  },
+  onDestroyCallback: function(aData) {
+    let tweet = JSON.parse(aData);
+    // If Twitter responds with an error, throw to call the error callback.
+    if ("error" in tweet)
+      throw tweet.error;
+
+    // Create a new system message saying the tweet has been deleted.
+    this.conversation.systemMessage(_("event.deleted", this.originalMessage));
+  }
+};
+
+function Action(aLabel, aAction, aTweet)
+{
+  this.label = aLabel;
+  this._action = aAction;
+  this._tweet = aTweet;
+}
+Action.prototype = {
+  __proto__: ClassInfo("prplIMessageAction", "generic message action object"),
+  get run() this._action.bind(this._tweet)
+};
+
+function Conversation(aAccount)
+{
+  this._init(aAccount);
+  this._ensureParticipantExists(aAccount.name);
+}
+Conversation.prototype = {
+  __proto__: GenericConvChatPrototype,
+  unInit: function() {
+    delete this._account._timeline;
+    GenericConvChatPrototype.unInit.call(this);
+  },
+  inReplyToStatusId: null,
+  startReply: function(aTweet) {
+    this.inReplyToStatusId = aTweet.id_str;
+    this.notifyObservers(null, "replying-to-prompt",
+                         "@" + aTweet.user.screen_name + " ");
+    this.notifyObservers(null, "status-text-changed",
+                         _("replyingToStatusText", aTweet.text));
+  },
+  reTweet: function(aTweet) {
+    this._account.reTweet(aTweet, this.onSentCallback,
+                          function(aException, aData) {
+      this.systemMessage(_("error.retweet", this._parseError(aData),
+                           aTweet.text), true);
+    }, this);
+  },
+  sendMsg: function (aMsg) {
+    if (aMsg.length > this._account.maxMessageLength) {
+      this.systemMessage(_("error.tooLong"), true);
+      throw Cr.NS_ERROR_INVALID_ARG;
+    }
+    this._account.tweet(aMsg, this.inReplyToStatusId, this.onSentCallback,
+                        function(aException, aData) {
+      let error = this._parseError(aData);
+      this.systemMessage(_("error.general", error, aMsg), true);
+    }, this);
+    this.sendTyping(0);
+  },
+  sendTyping: function(aLength) {
+    if (aLength == 0 && this.inReplyToStatusId) {
+      delete this.inReplyToStatusId;
+      this.notifyObservers(null, "status-text-changed", "");
+    }
+  },
+  systemMessage: function(aMessage, aIsError, aDate) {
+    let flags = {system: true};
+    if (aIsError)
+      flags.error = true;
+    if (aDate)
+      flags.time = aDate;
+    this.writeMessage("twitter.com", aMessage, flags);
+  },
+  onSentCallback: function(aData) {
+    let tweet = JSON.parse(aData);
+    if (tweet.user.screen_name != this._account.name)
+      throw "Wrong screen_name... Uh?";
+    this._account.displayMessages([tweet]);
+    this.setTopic(tweet.text, tweet.user.screen_name);
+  },
+  _parseError: function(aData) {
+    let error = "";
+    try {
+      let data = JSON.parse(aData);
+      if ("error" in data)
+        error = data.error;
+      else if ("errors" in data)
+        error = data.errors.split("\n")[0];
+      if (error)
+        error = "(" + error + ")";
+    } catch(e) {}
+    return error;
+  },
+  parseTweet: function(aTweet) {
+    let text = aTweet.text;
+    let entities = {};
+    // Handle retweets: retweeted_status contains the object for the original
+    // tweet that is being retweeted.
+    // If the retweet prefix ("RT @<username>: ") causes the tweet to be over
+    // 140 characters, ellipses will be added (and the truncated property is set
+    // to true). In this case, we want to get the FULL text from the original
+    // tweet and update the entities to match.
+    if ("retweeted_status" in aTweet && "truncated" in aTweet &&
+        aTweet["truncated"]) {
+      let retweet = aTweet["retweeted_status"];
+      // We're going to take portions of the retweeted status and replace parts
+      // of the original tweet, the retweeted status prepends the original
+      // status with "RT @<username>: ", we need to keep the prefix.
+      let offset = text.indexOf(": ") + 2;
+      text = text.slice(0, offset) + retweet.text;
+
+      // Keep any entities that refer to the prefix (we can refer directly to
+      // aTweet for these since they are not edited).
+      if ("entities" in aTweet) {
+        for (let type in aTweet.entities) {
+          let filteredEntities =
+            aTweet.entities[type].filter(function(e) e.indices[0] < offset);
+          if (filteredEntities.length)
+            entities[type] = filteredEntities;
+        }
+      }
+
+      // Add the entities from the retweet (a copy of these must be made since
+      // they will be edited and we do not wish to change aTweet).
+      if ("entities" in retweet) {
+        for (let type in retweet.entities) {
+          if (!(type in entities))
+            entities[type] = [];
+
+          // Append the entities from the original status.
+          entities[type] = entities[type].concat(
+            retweet.entities[type].map(function(aEntity) {
+              let entity = Object.create(aEntity);
+              // Add the offset to the indices to account for the prefix.
+              entity.indices = entity.indices.map(function(i) i + offset);
+              return entity;
+            })
+          );
+        }
+      }
+    } else {
+      // For non-retweets, we just want to use the entities that are given.
+      if ("entities" in aTweet)
+        entities = aTweet.entities;
+    }
+
+    if (Object.keys(entities).length) {
+      /* entArray is an array of entities ready to be replaced in the tweet,
+       * each entity contains:
+       *  - start: the start index of the entity inside the tweet,
+       *  - end: the end index of the entity inside the tweet,
+       *  - str: the string that should be replaced inside the tweet,
+       *  - href: the url (href attribute) of the created link tag,
+       *  - [optional] text: the text to display for the link,
+       *     The original string (str) will be used if this is not set.
+       *  - [optional] title: the title attribute for the link.
+       */
+      let entArray = [];
+      if ("hashtags" in entities && Array.isArray(entities.hashtags)) {
+        entArray = entArray.concat(entities.hashtags.map(function(h) ({
+          start: h.indices[0],
+          end: h.indices[1],
+          str: "#" + h.text,
+          href: "https://twitter.com/#!/search?q=%23" + h.text})));
+      }
+      if ("urls" in entities && Array.isArray(entities.urls)) {
+        entArray = entArray.concat(entities.urls.map(function(u) ({
+          start: u.indices[0],
+          end: u.indices[1],
+          str: u.url,
+          text: u.display_url || u.url,
+          href: u.expanded_url || u.url})));
+      }
+      if ("user_mentions" in entities &&
+          Array.isArray(entities.user_mentions)) {
+        entArray = entArray.concat(entities.user_mentions.map(function(um) ({
+          start: um.indices[0],
+          end: um.indices[1],
+          str: "@" + um.screen_name,
+          title: um.name,
+          href: "https://twitter.com/" + um.screen_name})));
+      }
+      entArray.sort(function(a, b) a.start - b.start);
+      let offset = 0;
+      for each (let entity in entArray) {
+        let str = text.substring(offset + entity.start, offset + entity.end);
+        if (str[0] == "\uFF20") // ï¼  - unicode character similar to @
+          str = "@" + str.substring(1);
+        if (str[0] == "\uFF03") // # - unicode character similar to #
+          str = "#" + str.substring(1);
+        if (str.toLowerCase() != entity.str.toLowerCase())
+          continue;
+
+        let html = "<a href=\"" + entity.href + "\"";
+        if ("title" in entity)
+          html += " title=\"" + entity.title + "\"";
+        html += ">" + ("text" in entity ? entity.text : entity.str) + "</a>";
+        text = text.slice(0, offset + entity.start) + html +
+               text.slice(offset + entity.end);
+        offset += html.length - (entity.end - entity.start);
+      }
+    }
+
+    return text;
+  },
+  displayTweet: function(aTweet) {
+    let name = aTweet.user.screen_name;
+    this._ensureParticipantExists(name);
+    let text = this.parseTweet(aTweet);
+
+    let flags =
+      name == this._account.name ? {outgoing: true} : {incoming: true};
+    flags.time = Math.round(new Date(aTweet.created_at) / 1000);
+    flags._iconURL = aTweet.user.profile_image_url;
+    if (text.indexOf(this.nick) != -1)
+      flags.containsNick = true;
+
+    (new Tweet(aTweet, name, text, flags)).conversation = this;
+  },
+  _ensureParticipantExists: function(aNick) {
+    if (hasOwnProperty(this._participants, aNick))
+      return;
+
+    let chatBuddy = new ChatBuddy(aNick);
+    this._participants[aNick] = chatBuddy;
+    this.notifyObservers(new nsSimpleEnumerator([chatBuddy]),
+                         "chat-buddy-add");
+  },
+  get name() this.nick + " timeline",
+  get title() _("timeline", this.nick),
+  get nick() "@" + this._account.name
+};
+
+function Account(aProtocol, aImAccount)
+{
+  this._init(aProtocol, aImAccount);
+  this._knownMessageIds = {};
+  this._userInfo = {};
+}
+Account.prototype = {
+  __proto__: GenericAccountPrototype,
+
+  get maxMessageLength() 140,
+
+  consumerKey: "TSuyS1ieRAkB3qWv8yyEw",
+  consumerSecret: "DKtKaSf5a7pBNhdBsSZHTnI5Y03hRlPFYWmb4xXBlkU",
+  completionURI: "http://oauthcallback.local/",
+  baseURI: "https://api.twitter.com/",
+
+  // Use this to keep track of the pending timeline requests. We attempt to fetch
+  // home_timeline, @ mentions and tracked keywords (i.e. 3 timelines)
+  _pendingRequests: [],
+  _timelineBuffer: [],
+  _timelineAuthError: 0,
+
+  token: "",
+  tokenSecret: "",
+  connect: function() {
+    if (this.connected || this.connecting)
+      return;
+
+    this.reportConnecting();
+
+    // Read the OAuth token from the prefs
+    let prefValue = {};
+    try {
+      prefValue = JSON.parse(this.prefs.getCharPref("oauth"));
+    } catch(e) { }
+    if (prefValue.hasOwnProperty(this.consumerKey)) {
+      let result = prefValue[this.consumerKey];
+      this.token = result.oauth_token;
+      this.tokenSecret = result.oauth_token_secret;
+      if (result.screen_name && result.screen_name != this.name) {
+        this.onError(_("connection.error.userMismatch"));
+        return;
+      }
+    }
+
+    // Get a new token if needed...
+    if (!this.token || !this.tokenSecret) {
+      this.requestToken();
+      return;
+    }
+
+    LOG("Connecting using existing token");
+    this.getTimelines();
+  },
+
+  // Twitter doesn't broadcast the user's availability, so we can ignore
+  // imIUserStatusInfo's status notifications.
+  observe: function(aSubject, aTopic, aMsg) { },
+
+  signAndSend: function(aUrl, aHeaders, aPOSTData, aOnLoad, aOnError, aThis,
+                        aOAuthParams) {
+    const kChars =
+      "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
+    const kNonceLength = 6;
+    let nonce = "";
+    for (let i = 0; i < kNonceLength; ++i)
+      nonce += kChars[Math.floor(Math.random() * kChars.length)];
+
+    let params = (aOAuthParams || []).concat([
+      ["oauth_consumer_key", this.consumerKey],
+      ["oauth_nonce", nonce],
+      ["oauth_signature_method", "HMAC-SHA1"],
+      ["oauth_token", this.token],
+      ["oauth_timestamp", Math.floor(((new Date()).getTime()) / 1000)],
+      ["oauth_version", "1.0"]
+    ]);
+
+    function percentEncode(aString)
+      encodeURIComponent(aString).replace(/\!|\*|\'|\(|\)/g, function(m)
+        ({"!": "%21", "*": "%2A", "'": "%27", "(": "%28", ")": "%29"}[m]))
+
+    let dataParams = [];
+    let url = /^https?:/.test(aUrl) ? aUrl : this.baseURI + aUrl;
+    let urlSpec = url;
+    let queryIndex = url.indexOf("?");
+    if (queryIndex != -1) {
+      urlSpec = url.slice(0, queryIndex);
+      dataParams = url.slice(queryIndex + 1).split("&")
+                      .map(function(p) p.split("=").map(percentEncode));
+    }
+    let method = "GET";
+    if (aPOSTData) {
+      method = "POST";
+      aPOSTData.forEach(function (p) {
+        dataParams.push(p.map(percentEncode));
+      });
+    }
+
+    let signatureKey = this.consumerSecret + "&" + this.tokenSecret;
+    let signatureBase =
+      method + "&" + encodeURIComponent(urlSpec) + "&" +
+      params.concat(dataParams)
+            .sort(function(a,b) (a[0] < b[0]) ? -1 : (a[0] > b[0]) ? 1 : 0)
+            .map(function(p) p.map(encodeURIComponent).join("%3D"))
+            .join("%26");
+
+    let keyFactory = Cc["@mozilla.org/security/keyobjectfactory;1"]
+                     .getService(Ci.nsIKeyObjectFactory);
+    let hmac =
+      Cc["@mozilla.org/security/hmac;1"].createInstance(Ci.nsICryptoHMAC);
+    hmac.init(hmac.SHA1,
+              keyFactory.keyFromString(Ci.nsIKeyObject.HMAC, signatureKey));
+    // No UTF-8 encoding, special chars are already escaped.
+    let bytes = [b.charCodeAt() for each (b in signatureBase)];
+    hmac.update(bytes, bytes.length);
+    let signature = hmac.finish(true);
+
+    params.push(["oauth_signature", encodeURIComponent(signature)]);
+
+    let authorization =
+      "OAuth " + params.map(function (p) p[0] + "=\"" + p[1] + "\"").join(", ");
+    let headers = (aHeaders || []).concat([["Authorization", authorization]]);
+
+    return doXHRequest(url, headers, aPOSTData, aOnLoad, aOnError, aThis);
+  },
+  _parseURLData: function(aData) {
+    let result = {};
+    aData.split("&").forEach(function (aParam) {
+      let [key, value] = aParam.split("=");
+      result[key] = value;
+    });
+    return result;
+  },
+
+  tweet: function(aMsg, aInReplyToId, aOnSent, aOnError, aThis) {
+    let POSTData = [["status", aMsg]];
+    if (aInReplyToId)
+      POSTData.push(["in_reply_to_status_id", aInReplyToId]);
+    this.signAndSend("1/statuses/update.json?include_entities=1", null,
+                     POSTData, aOnSent, aOnError, aThis);
+  },
+  reTweet: function(aTweet, aOnSent, aOnError, aThis) {
+    let url =
+      "1/statuses/retweet/" + aTweet.id_str + ".json?include_entities=1";
+    this.signAndSend(url, null, [], aOnSent, aOnError, aThis);
+  },
+  destroy: function(aTweet, aOnSent, aOnError, aThis) {
+    let url =
+      "1/statuses/destroy/" + aTweet.id_str + ".json?include_entities=1";
+    this.signAndSend(url, null, [], aOnSent, aOnError, aThis);
+  },
+
+  _friends: null,
+  isFriend: function(aUser) {
+    if (!("id" in aUser) || // users from search API tweets don't have an id.
+        !this._friends) // null until data is received from the user stream.
+      return null;
+    //XXX Good enough for now, but if we ever call this from a loop,
+    // we should keep this._friends sorted and do a binary search.
+    return this._friends.indexOf(aUser.id) != -1;
+  },
+  follow: function(aUserName) {
+    this.signAndSend("1/friendships/create.json", null,
+                     [["screen_name", aUserName]]);
+  },
+  stopFollowing: function(aUserName) {
+    // friendships/destroy will return the user in case of success.
+    // Error cases would return a non 200 HTTP code and not call our callback.
+    this.signAndSend("1/friendships/destroy.json", null,
+                     [["screen_name", aUserName]], function(aData, aXHR) {
+      let user = JSON.parse(aData);
+      if (!("id" in user))
+        return; // Unexpected response...
+      this._friends = this._friends.filter(function(id) id != user.id);
+      let date = aXHR.getResponseHeader("Date");
+      this.timeline.systemMessage(_("event.unfollow", user.screen_name), false,
+                                  new Date(date) / 1000);
+    }, null, this);
+  },
+  addBuddy: function(aTag, aName) {
+    this.follow(aName);
+  },
+
+  getTimelines: function() {
+    this.reportConnecting(_("connection.requestTimelines"));
+
+    // If we have a last known message ID, append it as a get parameter.
+    let lastMsgParam = "";
+    if (this.prefs.prefHasUserValue("lastMessageId")) {
+      let lastMsgId = this.prefs.getCharPref("lastMessageId");
+      // Check that the ID is made up of all digits, otherwise the server will
+      // croak on our request.
+      if (/^\d+$/.test(lastMsgId))
+        lastMsgParam = "&since_id=" + lastMsgId;
+      else
+        WARN("invalid value for the lastMessageId preference: " + lastMsgId);
+    }
+    let getParams = "?include_entities=1&count=200" + lastMsgParam;
+    this._pendingRequests = [
+      this.signAndSend("1/statuses/home_timeline.json" + getParams, null, null,
+                       this.onTimelineReceived, this.onTimelineError, this),
+      this.signAndSend("1/statuses/mentions.json" + getParams, null, null,
+                       this.onTimelineReceived, this.onTimelineError, this)
+    ];
+
+    let track = this.getString("track");
+    if (track) {
+      getParams = "?q=" + track.split(",").join(" OR ") + lastMsgParam;
+      let url = "http://search.twitter.com/search.json" + getParams;
+      this._pendingRequests.push(doXHRequest(url, null, null,
+                                             this.onSearchResultsReceived,
+                                             this.onTimelineError, this));
+    }
+  },
+
+  get timeline() this._timeline || (this._timeline = new Conversation(this)),
+  displayMessages: function(aMessages) {
+    for each (let tweet in aMessages) {
+      if (!("user" in tweet) || !("text" in tweet) || !("id_str" in tweet) ||
+         tweet.id_str in this._knownMessageIds)
+        continue;
+      this._knownMessageIds[tweet.id_str] = tweet;
+      if ("description" in tweet.user)
+        this._userInfo[tweet.user.screen_name] = tweet.user;
+      this.timeline.displayTweet(tweet);
+    }
+  },
+
+  onTimelineError: function(aError, aResponseText, aRequest) {
+    ERROR(aError);
+    if (aRequest.status == 401)
+      ++this._timelineAuthError;
+    this._doneWithTimelineRequest(aRequest);
+  },
+
+  onSearchResultsReceived: function(aData, aRequest) {
+    // Parse the returned data
+    let data = JSON.parse(aData);
+    // Fix the results from the search API to match those of the REST API.
+    // See bug 1053.
+    if ("results" in data) {
+      data = data.results;
+      for each (let tweet in data) {
+        if (!("user" in tweet) && "from_user" in tweet) {
+          tweet.user = {screen_name: tweet.from_user,
+                        profile_image_url: tweet.profile_image_url};
+        }
+        if (!("entities" in tweet)) {
+          tweet.entities = {};
+          let text = tweet.text;
+          let match;
+          let hashTags = [];
+          // The \B (non-word boundary) ensures that the character
+          // right before the # is not a character commonly found in
+          // words. This should prevent us from matching part of URLs.
+          // For the text of the hashtag, the official ruby(!) implementation
+          // matches an arbitrary number of alphanumeric (or underscore)
+          // characters, but with at least one non-digit character
+          // (not necessarily at the beginning of the tag).
+          let re = /\B[##](\w*[A-Za-z_]\w*)/g;
+          while ((match = re.exec(text))) {
+            hashTags.push({text: match[1],
+                           indices: [re.lastIndex - match[0].length,
+                                     re.lastIndex]});
+          }
+          if (hashTags.length)
+            tweet.entities.hashtags = hashTags;
+
+          let mentions = [];
+          // The \B is here to avoid matching parts of email addresses.
+          // For the text of the username, the official ruby implementation
+          // matches 1 to 20 alphanumeric (or underscore) characters.
+          re = /\B[@ï¼ ](\w{1,20})/g;
+          while ((match = re.exec(text))) {
+            mentions.push({name: "", screen_name: match[1],
+                           indices: [re.lastIndex - match[0].length,
+                                     re.lastIndex]});
+          }
+          if (mentions.length)
+            tweet.entities.user_mentions = mentions;
+        }
+      }
+    }
+    this._timelineBuffer = this._timelineBuffer.concat(data);
+
+    this._doneWithTimelineRequest(aRequest);
+  },
+
+  onTimelineReceived: function(aData, aRequest) {
+    this._timelineBuffer = this._timelineBuffer.concat(JSON.parse(aData));
+    this._doneWithTimelineRequest(aRequest);
+  },
+
+  _doneWithTimelineRequest: function(aRequest) {
+    this._pendingRequests =
+      this._pendingRequests.filter(function (r) r !== aRequest);
+
+    // If we are still waiting for more data, return early
+    if (this._pendingRequests.length != 0)
+      return;
+
+    if (this._timelineAuthError >= 2) {
+      // 2 out of the 3 timeline requests are authenticated.
+      // With at least 2 '401 - Unauthorized' errors, we are sure
+      // that our OAuth token is consistently rejected.
+      delete this._timelineAuthError;
+      delete this._timelineBuffer;
+      delete this._pendingRequests;
+      delete this.token;
+      delete this.tokenSecret;
+      this.requestToken();
+      return;
+    }
+
+    // Less than 2 auth errors is probably just some flakiness of the
+    // twitter servers, ignore and reset this._timelineAuthError.
+    if (this._timelineAuthError)
+      delete this._timelineAuthError;
+
+    this.reportConnected();
+
+    // If the conversation already exists, notify it we are back online.
+    if (this._timeline)
+      this._timeline.notifyObservers(this._timeline, "update-buddy-status");
+
+    this._timelineBuffer.sort(this.sortByDate);
+    this.displayMessages(this._timelineBuffer);
+
+    // Use the users' newest tweet as the topic.
+    for (let i = this._timelineBuffer.length - 1; i >= 0; --i) {
+      let tweet = this._timelineBuffer[i];
+      if (tweet.user.screen_name == this.name) {
+        this.timeline.setTopic(tweet.text, tweet.user.screen_name);
+        break;
+      }
+    }
+
+    // Reset in case we get disconnected
+    delete this._timelineBuffer;
+    delete this._pendingRequests;
+
+    // Open the streams to get the live data.
+    this.openStream();
+  },
+
+  sortByDate: function(a, b)
+    (new Date(a["created_at"])) - (new Date(b["created_at"])),
+
+  _streamingRequest: null,
+  _pendingData: "",
+  _receivedLength: 0,
+  openStream: function() {
+    let track = this.getString("track");
+    this._streamingRequest =
+      this.signAndSend("https://userstream.twitter.com/2/user.json",
+                       null, track ? [["track", track]] : [],
+                       this.openStream, this.onStreamError, this);
+    this._streamingRequest.onprogress = this.onDataAvailable.bind(this);
+  },
+  onStreamError: function(aError) {
+    delete this._streamingRequest;
+    this.gotDisconnected(Ci.prplIAccount.ERROR_NETWORK_ERROR, aError);
+  },
+  onDataAvailable: function(aRequest) {
+    let text = aRequest.target.responseText;
+    let newText = this._pendingData + text.slice(this._receivedLength);
+    DEBUG("Received data: " + newText);
+    let messages = newText.split(/\r\n?/);
+    this._pendingData = messages.pop();
+    this._receivedLength = text.length;
+    for each (let message in messages) {
+      if (!message.trim())
+        continue;
+      let msg;
+      try {
+        msg = JSON.parse(message);
+      } catch (e) {
+        ERROR(e + " while parsing " + message);
+        continue;
+      }
+      if ("text" in msg) {
+        this.displayMessages([msg]);
+        // If the message is from us, set it as the topic.
+        if (("user" in msg) && (msg.user.screen_name == this.name))
+          this.timeline.setTopic(msg.text, msg.user.screen_name);
+      }
+      else if ("friends" in msg)
+        this._friends = msg.friends;
+      else if (("event" in msg) && msg.event == "follow") {
+        let user, event;
+        if (msg.source.screen_name == this.name) {
+          this._friends.push(msg.target.id);
+          user = msg.target;
+          event = "follow";
+        }
+        else if (msg.target.screen_name == this.name) {
+          user = msg.source;
+          event = "followed";
+        }
+        if (user) {
+          this._userInfo[user.screen_name] = user;
+          this.timeline.systemMessage(_("event." + event, user.screen_name),
+                                      false, new Date(msg.created_at) / 1000);
+        }
+      }
+    }
+  },
+
+  requestToken: function() {
+    this.reportConnecting(_("connection.initAuth"));
+    let oauthParams =
+      [["oauth_callback", encodeURIComponent(this.completionURI)]];
+    this.signAndSend("oauth/request_token", null, [],
+                     this.onRequestTokenReceived, this.onError, this,
+                     oauthParams);
+  },
+  onRequestTokenReceived: function(aData) {
+    LOG("Received request token.");
+    let data = this._parseURLData(aData);
+    if (!data.oauth_callback_confirmed ||
+        !data.oauth_token || !data.oauth_token_secret) {
+      this.gotDisconnected(Ci.prplIAccount.ERROR_OTHER_ERROR,
+                           _("connection.failedToken"));
+      return;
+    }
+    this.token = data.oauth_token;
+    this.tokenSecret = data.oauth_token_secret;
+
+    this.requestAuthorization();
+  },
+  requestAuthorization: function() {
+    this.reportConnecting(_("connection.requestAuth"));
+    let url = this.baseURI + "oauth/authorize?oauth_token=";
+    this._browserRequest = {
+      get promptText() _("authPrompt"),
+      account: this,
+      url: url + this.token,
+      _active: true,
+      cancelled: function() {
+        if (!this._active)
+          return;
+
+        this.account
+            .gotDisconnected(Ci.prplIAccount.ERROR_AUTHENTICATION_FAILED,
+                             _("connection.error.authCancelled"));
+      },
+      loaded: function(aWindow, aWebProgress) {
+        if (!this._active)
+          return;
+
+        this._listener = {
+          QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+                                                 Ci.nsISupportsWeakReference]),
+          _cleanUp: function() {
+            this.webProgress.removeProgressListener(this);
+            this.window.close();
+            delete this.window;
+          },
+          _checkForRedirect: function(aURL) {
+            if (aURL.indexOf(this._parent.completionURI) != 0)
+              return;
+
+            this._parent.finishAuthorizationRequest();
+            this._parent.onAuthorizationReceived(aURL);
+          },
+          onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) {
+            const wpl = Ci.nsIWebProgressListener;
+            if (aStateFlags & (wpl.STATE_START | wpl.STATE_IS_NETWORK))
+              this._checkForRedirect(aRequest.name);
+          },
+          onLocationChange: function(aWebProgress, aRequest, aLocation) {
+            this._checkForRedirect(aLocation.spec);
+          },
+          onProgressChange: function() {},
+          onStatusChange: function() {},
+          onSecurityChange: function() {},
+
+          window: aWindow,
+          webProgress: aWebProgress,
+          _parent: this.account
+        };
+        aWebProgress.addProgressListener(this._listener,
+                                         Ci.nsIWebProgress.NOTIFY_ALL);
+      },
+      QueryInterface: XPCOMUtils.generateQI([Ci.prplIRequestBrowser])
+    };
+    Services.obs.notifyObservers(this._browserRequest, "browser-request", null);
+  },
+  finishAuthorizationRequest: function() {
+    if (!("_browserRequest" in this))
+      return;
+
+    this._browserRequest._active = false;
+    if ("_listener" in this._browserRequest)
+      this._browserRequest._listener._cleanUp();
+    delete this._browserRequest;
+  },
+  onAuthorizationReceived: function(aData) {
+    let data = this._parseURLData(aData.split("?")[1]);
+    if (data.oauth_token != this.token || !data.oauth_verifier) {
+      this.gotDisconnected(Ci.prplIAccount.ERROR_OTHER_ERROR,
+                           _("connection.error.authFailed"));
+      return;
+    }
+    this.requestAccessToken(data.oauth_verifier);
+  },
+  requestAccessToken: function(aTokenVerifier) {
+    this.reportConnecting(_("connection.requestAccess"));
+    this.signAndSend("oauth/access_token", null, [],
+                     this.onAccessTokenReceived, this.onError, this,
+                     [["oauth_verifier", aTokenVerifier]]);
+  },
+  onAccessTokenReceived: function(aData) {
+    LOG("Received access token.");
+    let result = this._parseURLData(aData);
+    if (result.screen_name && result.screen_name != this.name) {
+      this.onError(_("connection.error.userMismatch"));
+      return;
+    }
+
+    let prefValue = {};
+    try {
+      JSON.parse(this.prefs.getCharPref("oauth"));
+    } catch(e) { }
+    prefValue[this.consumerKey] = result;
+    this.prefs.setCharPref("oauth", JSON.stringify(prefValue));
+
+    this.token = result.oauth_token;
+    this.tokenSecret = result.oauth_token_secret;
+
+    this.getTimelines();
+  },
+
+
+  cleanUp: function() {
+    this.finishAuthorizationRequest();
+    if (this._pendingRequests.length != 0) {
+      for each (let request in this._pendingRequests)
+        request.abort();
+      delete this._pendingRequests;
+    }
+    if (this._streamingRequest) {
+      this._streamingRequest.abort();
+      delete this._streamingRequest;
+    }
+    delete this.token;
+    delete this.tokenSecret;
+  },
+  gotDisconnected: function(aError, aErrorMessage) {
+    if (this.disconnected || this.disconnecting)
+      return;
+
+    if (aError === undefined)
+      aError = Ci.prplIAccount.NO_ERROR;
+    let connected = this.connected;
+    this.reportDisconnecting(aError, aErrorMessage);
+    this.cleanUp();
+    if (this._timeline && connected)
+      this._timeline.notifyObservers(this._timeline, "update-conv-chatleft");
+    this.reportDisconnected();
+  },
+  unInit: function() {
+    this.cleanUp();
+    // If we've received any messages, update the last known message.
+    let newestMessageId = "";
+    for (let id in this._knownMessageIds) {
+      // Compare the length of the ids first, and then the text.
+      // This avoids converting tweet ids into rounded numbers.
+      if (id.length > newestMessageId.length ||
+          (id.length == newestMessageId.length && id > newestMessageId))
+        newestMessageId = id;
+    }
+    if (newestMessageId)
+      this.prefs.setCharPref("lastMessageId", newestMessageId);
+  },
+  disconnect: function() {
+    this.gotDisconnected();
+  },
+
+  onError: function(aException) {
+    if (aException == "offline") {
+      this.gotDisconnected(Ci.prplIAccount.ERROR_NETWORK_ERROR,
+                           _("connection.error.noNetwork"));
+    }
+    else
+      this.gotDisconnected(Ci.prplIAccount.ERROR_OTHER_ERROR, aException.toString());
+  },
+
+  onRequestedInfoReceived: function(aData) {
+    let user = JSON.parse(aData);
+    this._userInfo[user.screen_name] = user;
+    this.requestBuddyInfo(user.screen_name);
+  },
+  requestBuddyInfo: function(aBuddyName) {
+    if (!hasOwnProperty(this._userInfo, aBuddyName)) {
+      this.signAndSend("1/users/show.json?screen_name=" + aBuddyName, null,
+                       null, this.onRequestedInfoReceived, null, this);
+      return;
+    }
+
+    let userInfo = this._userInfo[aBuddyName];
+
+    // List of the names of the info to actually show in the tooltip and
+    // optionally a transform function to apply to the value.
+    // See https://dev.twitter.com/docs/api/1/get/users/show for the options.
+    let normalizeBool = function(isFollowing) _(isFollowing ? "yes" : "no");
+    const kFields = {
+      following: normalizeBool,
+      description: null,
+      url: null,
+      location: null,
+      lang: function(aLang) {
+        try {
+          return _lang(aLang);
+        }
+        catch(e) {
+          return aLang;
+        }
+      },
+      time_zone: null,
+      protected: normalizeBool,
+      created_at: function(aDate) (new Date(aDate)).toLocaleDateString(),
+      statuses_count: null,
+      friends_count: null,
+      followers_count: null,
+      listed_count: null
+    };
+
+    let tooltipInfo = [];
+    for (let field in kFields) {
+      if (hasOwnProperty(userInfo, field) && userInfo[field]) {
+        let value = userInfo[field];
+        if (kFields[field])
+          value = kFields[field](value);
+        tooltipInfo.push(new TooltipInfo(_("tooltip." + field), value));
+      }
+    }
+
+    Services.obs.notifyObservers(new nsSimpleEnumerator(tooltipInfo),
+                                 "user-info-received", aBuddyName);
+  },
+
+  // Allow us to reopen the timeline via the join chat menu.
+  get canJoinChat() true,
+  joinChat: function(aComponents) {
+    // The 'timeline' getter opens a timeline conversation if none exists.
+    this.timeline;
+  }
+};
+
+function TwitterProtocol() { }
+TwitterProtocol.prototype = {
+  __proto__: GenericProtocolPrototype,
+  get name() "Twitter",
+  get iconBaseURI() "chrome://prpl-twitter/skin/",
+  get noPassword() true,
+  options: {
+    "track": {get label() _("options.track"), default: ""}
+  },
+  getAccount: function(aImAccount) new Account(this, aImAccount),
+  classID: Components.ID("{31082ff6-1de8-422b-ab60-ca0ac0b2af13}")
+};
+
+const NSGetFactory = XPCOMUtils.generateNSGetFactory([TwitterProtocol]);
new file mode 100644
--- /dev/null
+++ b/chat/protocols/twitter/twitter.manifest
@@ -0,0 +1,3 @@
+component {31082ff6-1de8-422b-ab60-ca0ac0b2af13} twitter.js
+contract @mozilla.org/chat/twitter;1 {31082ff6-1de8-422b-ab60-ca0ac0b2af13}
+category im-protocol-plugin prpl-twitter @mozilla.org/chat/twitter;1
new file mode 100644
--- /dev/null
+++ b/chat/protocols/xmpp/Makefile.in
@@ -0,0 +1,56 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is
+# Florian Quèze <florian@queze.net>.
+# Portions created by the Initial Developer are Copyright (C) 2011
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either of the GNU General Public License Version 2 or later (the "GPL"),
+# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH		= ../../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+EXTRA_COMPONENTS = \
+		xmpp.js \
+		xmpp.manifest \
+		$(NULL)
+
+EXTRA_JS_MODULES = \
+		xmpp.jsm \
+		xmpp-authmechs.jsm \
+		xmpp-session.jsm \
+		xmpp-xml.jsm \
+		$(NULL)
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/chat/protocols/xmpp/xmpp-authmechs.jsm
@@ -0,0 +1,169 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Instantbird.
+ *
+ * The Initial Developer of the Original Code is
+ * Varuna JAYASIRI <vpjayasiri@gmail.com>.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Florian Quèze <florian@queze.net>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// This module exports XMPPAuthMechanisms, an object containing all
+// the supported SASL authentication mechanisms.
+// By default we currently support the PLAIN and the DIGEST-MD5 mechanisms.
+// As this is only used by XMPPSession, it may seem like an internal
+// detail of the XMPP implementation, but exporting it is valuable so that
+// add-ons can add support for more auth mechanisms easily by adding them
+// in XMPPAuthMechanisms without having to modify XMPPSession.
+
+const EXPORTED_SYMBOLS = ["XMPPAuthMechanisms"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource:///modules/xmpp-xml.jsm");
+
+/* Handle PLAIN authorization mechanism */
+function PlainAuth(username, password, domain) {
+  this._username = username;
+  this._password = password;
+}
+PlainAuth.prototype = {
+  next: function(aStanza) ({
+    done: true,
+    send: Stanza.node("auth", Stanza.NS.sasl, {mechanism: "PLAIN"},
+                      btoa("\0"+ this._username + "\0" + this._password))
+  })
+};
+
+
+/* Handles DIGEST-MD5 authorization mechanism */
+
+// md5 function adapted from netwerk/test/unit/test_authentication.js
+// If aUTF8 is true, aString will be treated as an UTF8 encoded string,
+// otherwise it can contain binary data.
+function md5(aString, aUTF8) {
+  let ch = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
+  ch.init(ch.MD5);
+
+  let data;
+  if (aUTF8) {
+    let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+                      .createInstance(Ci.nsIScriptableUnicodeConverter);
+    converter.charset = "UTF-8";
+    data = converter.convertToByteArray(aString);
+  }
+  else
+    data = [aString.charCodeAt(i) for (i in aString)];
+
+  ch.update(data, data.length);
+  return ch.finish(false);
+}
+function md5hex(aString) {
+  let hash = md5(aString);
+  function toHexString(charCode) ("0" + charCode.toString(16)).slice(-2)
+  return [toHexString(hash.charCodeAt(i)) for (i in hash)].join("");
+}
+
+function digestMD5(aName, aRealm, aPassword, aNonce, aCnonce, aDigestUri) {
+  let y = md5(aName + ":" + aRealm + ":" + aPassword, true);
+  return md5hex(md5hex(y + ":" + aNonce + ":" + aCnonce) +
+                ":" + aNonce + ":00000001:" + aCnonce + ":auth:" +
+                md5hex("AUTHENTICATE:" + aDigestUri));
+}
+
+function DigestMD5Auth(username, password, domain) {
+  this._username = username;
+  this._password = password;
+  this._domain = domain;
+  this.next = this._init;
+}
+DigestMD5Auth.prototype = {
+  _init: function(aStanza) {
+    this.next = this._generateResponse;
+    return {
+      done: false,
+      send: Stanza.node("auth", Stanza.NS.sasl, {mechanism: "DIGEST-MD5"})
+    };
+  },
+
+  _generateResponse: function(aStanza) {
+    let decoded = atob(aStanza.innerText.replace(/[^A-Za-z0-9\+\/\=]/g, ""));
+    let data = {realm: ""};
+
+    for each (let elem in decoded.split(",")) {
+      let e = elem.split("=");
+      if (e.length != 2)
+        throw "Error decoding: " + elem;
+
+      data[e[0]] = e[1].replace(/"|'/g, "");
+    }
+
+    data.username = this._username;
+
+    const kChars =
+      "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
+    const kNonceLength = 32;
+    let nonce = "";
+    for (let i = 0; i < kNonceLength; ++i)
+      nonce += kChars[Math.floor(Math.random() * kChars.length)];
+
+    data.cnonce = nonce;
+    data.nc = "00000001";
+    data.qop = "auth",
+    data["digest-uri"] = "xmpp/" + this._domain + (data.host ? "/" + host : "");
+    data.response = digestMD5(this._username, data.realm, this._password,
+                              data.nonce, data.cnonce, data["digest-uri"]);
+    data.charset = "utf-8";
+
+    let response =
+      ["username", "realm", "nonce", "cnonce", "nc", "qop", "digest-uri",
+       "response", "charset"].map(function(key) key + "=\"" + data[key] + "\"")
+                             .join(",");
+
+    this.next = this._finish;
+
+    return {
+      done: false,
+      send: Stanza.node("response", Stanza.NS.sasl, null, btoa(response))
+    };
+  },
+
+  _finish: function(aStanza) {
+    if (aStanza.localName != "challenge")
+      throw "Not authorized";
+
+    return {
+      done: true,
+      send: Stanza.node("response", Stanza.NS.sasl)
+    };
+  }
+};
+
+var XMPPAuthMechanisms = {"PLAIN": PlainAuth, "DIGEST-MD5": DigestMD5Auth};
new file mode 100644
--- /dev/null
+++ b/chat/protocols/xmpp/xmpp-session.jsm
@@ -0,0 +1,406 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Instantbird.
+ *
+ * The Initial Developer of the Original Code is
+ * Varuna JAYASIRI <vpjayasiri@gmail.com>.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Florian Quèze <florian@queze.net>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const EXPORTED_SYMBOLS = ["XMPPSession", "XMPPDefaultResource"];
+
+const {interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource:///modules/imXPCOMUtils.jsm");
+Cu.import("resource:///modules/socket.jsm");
+Cu.import("resource:///modules/xmpp-xml.jsm");
+Cu.import("resource:///modules/xmpp-authmechs.jsm");
+
+initLogModule("xmpp-session", this);
+
+XPCOMUtils.defineLazyGetter(this, "_", function()
+  l10nHelper("chrome://chat/locale/xmpp.properties")
+);
+
+// Workaround because a lazy getter can't be exported.
+XPCOMUtils.defineLazyGetter(this, "_defaultResource", function()
+  l10nHelper("chrome://branding/locale/brand.properties")("brandShortName")
+);
+__defineGetter__("XMPPDefaultResource", function() _defaultResource);
+
+function XMPPSession(aHost, aPort, aSecurity, aJID, aPassword, aAccount) {
+  this._host = aHost;
+  this._port = aPort;
+
+  this._connectionSecurity = aSecurity;
+  if (this._connectionSecurity == "old_ssl")
+    this._security = ["ssl"];
+  else if (this._connectionSecurity != "none")
+    this._security = [(aPort == 5223 || aPort == 443) ? "ssl" : "starttls"];
+
+  this._jid = aJID;
+  this._domain = aJID.domain;
+  this._password = aPassword;
+  this._account = aAccount;
+
+  this._auth = null;
+  this._resource = aJID.resource || XMPPDefaultResource;
+  this._handlers = {};
+  this._stanzaId = 0;
+
+  this._account.reportConnecting();
+  try {
+    this.connect(this._host, this._port, this._security);
+  } catch (e) {
+    Cu.reportError(e);
+    // We can't use _networkError because this._account._connection
+    // isn't set until we return from the XMPPSession constructor.
+    this._account.reportDisconnecting(Ci.prplIAccount.ERROR_NETWORK_ERROR,
+                                      _("connection.error.failedToCreateASocket"));
+    this._account.reportDisconnected();
+  }
+}
+
+XMPPSession.prototype = {
+  /* for the socket.jsm helper */
+  __proto__: Socket,
+  connectTimeout: 60,
+  readWriteTimeout: 0,
+
+  _security: null,
+  _encrypted: false,
+
+  /* Disconnect from the server */
+  disconnect: function() {
+    if (this.onXmppStanza == this.stanzaListeners.accountListening)
+      this.send("</stream:stream>");
+    delete this.onXmppStanza;
+    Socket.disconnect.call(this);
+    if (this._parser) {
+      this._parser.destroy();
+      delete this._parser;
+      if (this._oldParsers) {
+        for each (let parser in this._oldParsers)
+          parser.destroy();
+        delete this._oldParsers;
+      }
+    }
+  },
+
+  /* Report errors to the account */
+  onError: function(aError, aException) {
+    this._account.onError(aError, aException);
+  },
+
+  /* Send a text message to the server */
+  send: function(aMsg) {
+    this.sendString(aMsg);
+  },
+
+  /* Send a stanza to the server.
+   * Can set a callback if required, which will be called
+   * when the server responds to the stanza with
+   * a stanza of the same id. */
+  sendStanza: function(aStanza, aCallback, aObject) {
+    if (!aStanza.attributes.hasOwnProperty("id"))
+      aStanza.attributes["id"] = ++this._stanzaId;
+    if (aCallback)
+      this.addHandler(aStanza.attributes.id, aCallback.bind(aObject));
+    this.send(aStanza.getXML());
+    return aStanza.attributes.id;
+  },
+
+
+  /* these 3 methods handle callbacks for specific ids. */
+  addHandler: function(aId, aCallback) {
+    this._handlers[aId] = aCallback;
+  },
+  removeHandler: function(aId) {
+    delete this._handlers[aId];
+  },
+  execHandler: function(aId, aStanza) {
+    if (!this._handlers.hasOwnProperty(aId))
+      return false;
+    this._handlers[aId](aStanza);
+    this.removeHandler(aId);
+    return true;
+  },
+
+  /* Start the XMPP stream */
+  startStream: function() {
+    if (this._parser) {
+      // nsSAXXMLReader (inside XMPPParser) leaks if we don't clean up.
+      // Unfortunately, calling onStopRequest on nsSAXXMLReader damages
+      // something that causes a crash the next time we call onDataAvailable
+      // on another parser instance for the same input stream buffer.
+      // Workaround: keep references to all previous parsers used
+      // for this socket, and call destroy on each of them when we are
+      // done reading from that socket.
+      if (!this._oldParsers)
+        this._oldParsers = [];
+      this._oldParsers.push(this._parser);
+    }
+    this._parser = new XMPPParser(this);
+    this.send('<?xml version="1.0"?><stream:stream to="' + this._domain +
+              '" xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" version="1.0">');
+  },
+
+  /* Log a message (called by the socket code) */
+  log: LOG,
+
+  /* Socket events */
+  /* The connection is established */
+  onConnection: function() {
+    if (this._security.indexOf("ssl") != -1) {
+      this.onXmppStanza = this.stanzaListeners.startAuth;
+      this._encrypted = true;
+    }
+    else
+      this.onXmppStanza = this.stanzaListeners.initStream;
+    this._account.reportConnecting(_("connection.initializingStream"));
+    this.startStream();
+  },
+
+  /* When incoming data is available to be read */
+  onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, aCount) {
+    try {
+      this._parser.onDataAvailable(aInputStream, aOffset, aCount);
+    } catch(e) {
+      Cu.reportError(e);
+      this.onXMLError("parser-exception", e);
+    }
+  },
+
+  /* The connection got disconnected without us closing it. */
+  onConnectionClosed: function() {
+    this._networkError(_("connection.error.serverClosedConnection"));
+  },
+  onConnectionReset: function() {
+    this._networkError(_("connection.error.resetByPeer"));
+  },
+  onConnectionTimedOut: function() {
+    this._networkError(_("connection.error.timedOut"));
+  },
+  _networkError: function(aMessage) {
+    this.onError(Ci.prplIAccount.ERROR_NETWORK_ERROR, aMessage);
+  },
+
+
+  /* Methods called by the XMPPParser instance */
+  onXMLError: function(aError, aException) {
+    if (aError == "parsing-characters")
+      WARN(aError + ": " + aException);
+    else
+      ERROR(aError + ": " + aException);
+    if (aError != "parse-warning" && aError != "parsing-characters")
+      this._networkError(_("connection.error.receivedUnexpectedData"));
+  },
+
+  // All the functions in stanzaListeners are used as onXmppStanza
+  // implementations at various steps of establishing the session.
+  stanzaListeners: {
+    initStream: function(aStanza) {
+      if (aStanza.localName != "features") {
+        ERROR("Unexpected stanza " + aStanza.localName + ", expected 'features'");
+        this._networkError(_("connection.error.incorrectResponse"));
+        return;
+      }
+
+      let starttls = aStanza.getElement(["starttls"]);
+      if (starttls && this._security.indexOf("starttls") != -1) {
+        this._account.reportConnecting(_("connection.initializingEncryption"));
+        this.sendStanza(Stanza.node("starttls", Stanza.NS.tls));
+        this.onXmppStanza = this.stanzaListeners.startTLS;
+        return;
+      }
+      if (starttls &&
+          starttls.children.some(function (c) c.localName == "required")) {
+        this.onError(Ci.prplIAccount.ERROR_ENCRYPTION_ERROR,
+                     _("connection.error.startTLSRequired"));
+        return;
+      }
+      if (!starttls && this._connectionSecurity == "require_tls") {
+        this.onError(Ci.prplIAccount.ERROR_ENCRYPTION_ERROR,
+                     _("connection.error.startTLSNotSupported"));
+        return;
+      }
+
+      // If we aren't starting TLS, jump to the auth step.
+      this.onXmppStanza = this.stanzaListeners.startAuth;
+      this.onXmppStanza(aStanza);
+    },
+    startTLS: function(aStanza) {
+      if (aStanza.localName != "proceed") {
+        this._networkError(_("connection.error.failedToStartTLS"));
+        return;
+      }
+
+      this.startTLS();
+      this._encrypted = true;
+      this.startStream();
+      this.onXmppStanza = this.stanzaListeners.startAuth;
+    },
+    startAuth: function(aStanza) {
+      if (aStanza.localName != "features") {
+        ERROR("Unexpected stanza " + aStanza.localName + ", expected 'features'");
+        this._networkError(_("connection.error.incorrectResponse"));
+        return;
+      }
+
+      let mechs = aStanza.getElement(["mechanisms"]);
+      if (!mechs) {
+        this._networkError(_("connection.error.noAuthMec"));
+        return;
+      }
+
+      // Select the auth mechanism we will use. PLAIN will be treated
+      // a bit differently as we want to avoid it over an unencrypted
+      // connection, except if the user has explicly allowed that
+      // behavior.
+      let selectedMech = "";
+      let canUsePlain = false;
+      mechs = mechs.getChildren("mechanism");
+      for each (let m in mechs) {
+        let mech = m.innerText;
+        if (mech == "PLAIN" && !this._encrypted)
+          canUsePlain = true;
+        else if (XMPPAuthMechanisms.hasOwnProperty(mech)) {
+          selectedMech = mech;
+          break;
+        }
+      }
+      if (!selectedMech && canUsePlain) {
+        if (this._security == "allow_unencrypted_plain_auth")
+          selectedMech = "PLAIN";
+        else {
+          this.onError(Ci.prplIAccount.ERROR_AUTHENTICATION_IMPOSSIBLE,
+                       _("connection.error.notSendingPasswordInClear"));
+          return;
+        }
+      }
+      if (!selectedMech) {
+        this.onError(Ci.prplIAccount.ERROR_AUTHENTICATION_IMPOSSIBLE,
+                     _("connection.error.noCompatibleAuthMec"));
+        return;
+      }
+      this._auth = new XMPPAuthMechanisms[selectedMech](this._jid.node,
+                                                        this._password,
+                                                        this._domain);
+
+      this._account.reportConnecting(_("connection.authenticating"));
+      this.onXmppStanza = this.stanzaListeners.authDialog;
+      this.onXmppStanza(null); // the first auth step doesn't read anything
+    },
+    authDialog: function(aStanza) {
+      if (aStanza && aStanza.localName == "failure") {
+        let errorMsg = "authenticationFailure";
+        if (aStanza.getElement(["not-authorized"]))
+          errorMsg = "notAuthorized";
+        this.onError(Ci.prplIAccount.ERROR_AUTHENTICATION_FAILED,
+                     _("connection.error." + errorMsg));
+        return;
+      }
+
+      let result;
+      try {
+        result = this._auth.next(aStanza);
+      } catch(e) {
+        ERROR(e);
+        this.onError(Ci.prplIAccount.ERROR_AUTHENTICATION_FAILED,
+                     _("connection.error.authenticationFailure"));
+        return;
+      }
+
+      if (result.send)
+        this.send(result.send.getXML());
+      if (result.done)
+        this.onXmppStanza = this.stanzaListeners.authResult;
+    },
+    authResult: function(aStanza) {
+      if (aStanza.localName != "success") {
+        this.onError(Ci.prplIAccount.ERROR_AUTHENTICATION_FAILED,
+                     _("connection.error.notAuthorized"));
+        return;
+      }
+
+      this.startStream();
+      this.onXmppStanza = this.stanzaListeners.startBind;
+    },
+    startBind: function(aStanza) {
+      if (!aStanza.getElement(["bind"])) {
+        ERROR("Unexpected lack of the bind feature");
+        this._networkError(_("connection.error.incorrectResponse"));
+        return;
+      }
+
+      this._account.reportConnecting(_("connection.gettingResource"));
+      this.sendStanza(Stanza.iq("set", null, null,
+                                Stanza.node("bind", Stanza.NS.bind, null,
+                                            Stanza.node("resource", null, null,
+                                                        this._resource))));
+      this.onXmppStanza = this.stanzaListeners.bindResult;
+    },
+    bindResult: function(aStanza) {
+      let jid = aStanza.getElement(["bind", "jid"]);
+      if (!jid) {
+        this._networkError(_("connection.error.failedToGetAResource"));
+        return;
+      }
+      jid = jid.innerText;
+      DEBUG("jid = " + jid);
+      this._jid = this._account._parseJID(jid);
+      this.sendStanza(Stanza.iq("set", null, null,
+                                Stanza.node("session", Stanza.NS.session)));
+      this.onXmppStanza = this.stanzaListeners.sessionStarted;
+    },
+    sessionStarted: function(aStanza) {
+      this._account.onConnection();
+      this.onXmppStanza = this.stanzaListeners.accountListening;
+    },
+    accountListening: function(aStanza) {
+      let handled = false;
+      if (aStanza.attributes.id)
+        handled = this.execHandler(aStanza.attributes.id, aStanza);
+
+      this._account.onXmppStanza(aStanza, handled);
+      let name = aStanza.qName;
+      if (name == "presence")
+        this._account.onPresenceStanza(aStanza, handled);
+      else if (name == "message")
+        this._account.onMessageStanza(aStanza, handled);
+      else if (name == "iq")
+        this._account.onIQStanza(aStanza, handled);
+    }
+  },
+  onXmppStanza: function(aStanza) {
+    ERROR("should not be reached\n");
+  }
+};
new file mode 100644
--- /dev/null
+++ b/chat/protocols/xmpp/xmpp-xml.jsm
@@ -0,0 +1,448 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Instantbird.
+ *
+ * The Initial Developer of the Original Code is
+ * Varuna JAYASIRI <vpjayasiri@gmail.com>.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Florian Quèze <florian@queze.net>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const EXPORTED_SYMBOLS = ["Stanza", "XMPPParser"];
+
+const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
+
+const NS = {
+  xml                       : "http://www.w3.org/XML/1998/namespace",
+  xhtml                     : "http://www.w3.org/1999/xhtml",
+  xhtml_im                  : "http://jabber.org/protocol/xhtml-im",
+
+  //auth
+  client                    : "jabber:client",
+  streams                   : "http://etherx.jabber.org/streams",
+  stream                    : "urn:ietf:params:xml:ns:xmpp-streams",
+  sasl                      : "urn:ietf:params:xml:ns:xmpp-sasl",
+  tls                       : "urn:ietf:params:xml:ns:xmpp-tls",
+  bind                      : "urn:ietf:params:xml:ns:xmpp-bind",
+  session                   : "urn:ietf:params:xml:ns:xmpp-session",
+  auth                      : "jabber:iq:auth",
+  http_bind                 : "http://jabber.org/protocol/httpbind",
+  http_auth                 : "http://jabber.org/protocol/http-auth",
+  xbosh                     : "urn:xmpp:xbosh",
+
+  "private"                 : "jabber:iq:private",
+  xdata                     : "jabber:x:data",
+
+  //roster
+  roster                    : "jabber:iq:roster",
+  roster_versioning         : "urn:xmpp:features:rosterver",
+  roster_delimiter          : "roster:delimiter",
+
+  //privacy lists
+  privacy                   : "jabber:iq:privacy",
+
+  //discovering
+  disco_info                : "http://jabber.org/protocol/disco#info",
+  disco_items               : "http://jabber.org/protocol/disco#items",
+  caps                      : "http://jabber.org/protocol/caps",
+
+  //addressing
+  address                   : "http://jabber.org/protocol/address",
+
+  muc_user                  : "http://jabber.org/protocol/muc#user",
+  muc                       : "http://jabber.org/protocol/muc",
+  register                  : "jabber:iq:register",
+  delay                     : "urn:xmpp:delay",
+  delay_legacy              : "jabber:x:delay",
+  bookmarks                 : "storage:bookmarks",
+  chatstates                : "http://jabber.org/protocol/chatstates",
+  event                     : "jabber:x:event",
+  stanzas                   : "urn:ietf:params:xml:ns:xmpp-stanzas",
+  vcard                     : "vcard-temp",
+  vcard_update              : "vcard-temp:x:update",
+  ping                      : "urn:xmpp:ping",
+
+  geoloc                    : "http://jabber.org/protocol/geoloc",
+  geoloc_notify             : "http://jabber.org/protocol/geoloc+notify",
+  mood                      : "http://jabber.org/protocol/mood",
+  tune                      : "http://jabber.org/protocol/tune",
+  nick                      : "http://jabber.org/protocol/nick",
+  nick_notify               : "http://jabber.org/protocol/nick+notify",
+  activity                  : "http://jabber.org/protocol/activity",
+  last                      : "jabber:iq:last",
+  avatar_data               : "urn:xmpp:avatar:data",
+  avatar_data_notify        : "urn:xmpp:avatar:data+notify",
+  avatar_metadata           : "urn:xmpp:avatar:metadata",
+  avatar_metadata_notify    : "urn:xmpp:avatar:metadata+notify",
+  pubsub                    : "http://jabber.org/protocol/pubsub",
+  pubsub_event              : "http://jabber.org/protocol/pubsub#event"
+};
+
+
+var TOP_LEVEL_ELEMENTS = {
+  "message"             : "jabber:client",
+  "presence"            : "jabber:client",
+  "iq"                  : "jabber:client",
+  "stream:features"     : "http://etherx.jabber.org/streams",
+  "proceed"             : "urn:ietf:params:xml:ns:xmpp-tls",
+  "failure"             : ["urn:ietf:params:xml:ns:xmpp-tls",
+                           "urn:ietf:params:xml:ns:xmpp-sasl"],
+  "success"             : "urn:ietf:params:xml:ns:xmpp-sasl",
+  "challenge"           : "urn:ietf:params:xml:ns:xmpp-sasl",
+  "error"               : "urn:ietf:params:xml:ns:xmpp-streams"
+};
+
+/* Stanza Builder */
+const Stanza = {
+  NS: NS,
+
+  /* Create a presence stanza */
+  presence: function(aAttr, aData) Stanza.node("presence", null, aAttr, aData),
+
+  /* Create a message stanza */
+  message: function(aTo, aMsg, aState, aAttr, aData) {
+    if (!aAttr)
+      aAttr = {};
+
+    aAttr.to = aTo;
+    if (!("type" in aAttr))
+      aAttr.type = "chat";
+
+    if (!aData)
+      aData = [];
+
+    if (aMsg)
+      aData.push(Stanza.node("body", null, null, aMsg));
+
+    if (aState)
+      aData.push(Stanza.node(aState, Stanza.NS.chatstates));
+
+    return Stanza.node("message", null, aAttr, aData);
+  },
+
+  /* Create a iq stanza */
+  iq: function(aType, aId, aTo, aData) {
+    let attrs = {type: aType};
+    if (aId)
+      attrs.id = aId;
+    if (aTo)
+      attrs.to = aTo;
+    return this.node("iq", null, attrs, aData);
+  },
+
+  /* Create a XML node */
+  node: function(aName, aNs, aAttr, aData) {
+    let n = new XMLNode(null, aNs, aName, aName, null);
+
+    if (aAttr) {
+      for (let at in aAttr)
+        n.attributes[at] = aAttr[at];
+    }
+
+    if (aData) {
+      if (!Array.isArray(aData))
+        aData = [aData];
+      for each (let child in aData)
+         n[typeof(child) == "string" ? "addText" : "addChild"](child);
+    }
+
+    return n;
+  }
+};
+
+/* Text node
+ * Contains a text */
+function TextNode(aText) {
+  this.text = aText;
+}
+
+TextNode.prototype = {
+  get type() "text",
+
+  append: function(aText) {
+    this.text += aText;
+  },
+
+  /* For debug purposes, returns an indented (unencoded) string */
+  convertToString: function(aIndent) aIndent + this.text + "\n",
+
+  /* Returns the encoded XML */
+  getXML: function()
+    Components.classes["@mozilla.org/txttohtmlconv;1"]
+              .getService(Ci.mozITXTToHTMLConv)
+              .scanTXT(this.text, Ci.mozITXTToHTMLConv.kEntities),
+
+  /* To read the unencoded data. */
+  get innerText() this.text
+};
+
+/* XML node */
+function XMLNode(aParentNode, aUri, aLocalName, aQName, aAttr) {
+  this._parentNode = aParentNode; // Used only for parsing
+  this.uri = aUri;
+  this.localName = aLocalName;
+  this.qName = aQName;
+  this.attributes = {};
+  this.children = [];
+
+  if (aAttr) {
+    for (let i = 0; i < aAttr.length; ++i)
+      this.attributes[aAttr.getQName(i)] = aAttr.getValue(i);
+  }
+}
+
+XMLNode.prototype = {
+  get type() "node",
+
+  /* Add a new child node */
+  addChild: function(aNode) {
+    this.children.push(aNode);
+  },
+
+  /* Add text node */
+  addText: function(aText) {
+    let lastIndex = this.children.length - 1;
+    if (lastIndex >= 0 && this.children[lastIndex] instanceof TextNode)
+      this.children[lastIndex].append(aText);
+    else
+      this.children.push(new TextNode(aText));
+  },
+
+  /* Get child elements by namespace */
+  getChildrenByNS: function(aNS)
+    this.children.filter(function(c) c.uri == aNS),
+
+  /* Get the first element inside the node that matches a query. */
+  getElement: function(aQuery) {
+   if (aQuery.length == 0)
+     return this;
+
+   let nq = aQuery.slice(1);
+   for each (let child in this.children) {
+     if (child.qName != aQuery[0])
+       continue;
+     let n = child.getElement(nq);
+     if (n)
+       return n;
+   }
+
+   return null;
+  },
+
+  /* Get all elements matching the query */
+  getElements: function(aQuery) {
+   if (aQuery.length == 0)
+     return [this];
+
+   let c = this.getChildren(aQuery[0]);
+   let nq = aQuery.slice(1);
+   let res = [];
+   for each (let child in c) {
+     let n = child.getElements(nq);
+     res = res.concat(n);
+   }
+
+   return res;
+  },
+
+  /* Get immediate children by the node name */
+  getChildren: function(aName)
+    this.children.filter(function(c) c.qName == aName),
+
+  /* Test if the node is a stanza */
+  isXmppStanza: function() {
+    if (!TOP_LEVEL_ELEMENTS.hasOwnProperty(this.qName))
+      return false;
+    let ns = TOP_LEVEL_ELEMENTS[this.qName];
+    return ns == this.uri || (Array.isArray(ns) && ns.indexOf(this.uri) != -1);
+  },
+
+  /* Returns indented XML */
+  convertToString: function(aIndent) {
+    if (!aIndent)
+      aIndent = "";
+
+    let s =
+      aIndent + "<" + this.qName + this._getXmlns() + this._getAttributeText();
+    let content = "";
+    for each (let child in this.children)
+      content += child.convertToString(aIndent + " ");
+    return s + (content ? ">\n" + content + aIndent + "</" + this.qName : "/") + ">\n";
+  },
+
+  /* Returns the XML */
+  getXML: function() {
+    let s = "<" + this.qName + this._getXmlns() + this._getAttributeText();
+    let innerXML = this.children.map(function(c) c.getXML()).join("");
+    return s + (innerXML ? ">" + innerXML + "</" + this.qName : "/") + ">";
+  },
+
+  get innerText() this.children.map(function(c) c.innerText).join(""),
+
+  /* Private methods */
+  _getXmlns: function() this.uri ? " xmlns=\"" + this.uri + "\"" : "",
+  _getAttributeText: function() {
+    let s = "";
+    for (let name in this.attributes)
+      s += " " +name + "=\"" + this.attributes[name] + "\"";
+    return s;
+  }
+};
+
+function XMPPParser(aListener) {
+  this._parser = Cc["@mozilla.org/saxparser/xmlreader;1"]
+                 .createInstance(Ci.nsISAXXMLReader);
+  this._parser.contentHandler = this;
+  this._parser.errorHandler = this;
+  this._parser.parseAsync(null);
+  this._listener = aListener;
+  this._parser.onStartRequest(this._dummyRequest, null);
+}
+XMPPParser.prototype = {
+  _destroyPending: false,
+  destroy: function() {
+    // Avoid reference cycles
+    this._parser.contentHandler = null;
+    delete this._listener;
+    // Calling onStopRequest while we are in an onDataAvailable
+    // callback crashes, don't do it.
+    if (this._inOnDataAvailable) {
+      this._destroyPending = true;
+      return;
+    }
+    this._parser.onStopRequest(this._dummyRequest, null, Cr.NS_OK);
+    // Stopping the request causes parse errors (because we parsed
+    // only partial XML documents?), so the error handler is still
+    // needed to avoid the errors being reported to the error console.
+    this._parser.errorHandler = null;
+    delete this._parser;
+  },
+  _dummyRequest: {
+    cancel: function() { },
+    isPending: function() { },
+    resume: function() { },
+    suspend: function() { }
+  },
+
+  _inOnDataAvailable: false,
+  onDataAvailable: function(aInputStream, aOffset, aCount) {
+    this._inOnDataAvailable = true;
+    this._parser.onDataAvailable(this._dummyRequest, null,
+                                 aInputStream, aOffset, aCount);
+    delete this._inOnDataAvailable;
+    if (this._destroyPending)
+      this.destroy();
+  },
+
+  /* nsISAXContentHandler implementation */
+  startDocument: function() { },
+  endDocument: function() { },
+
+  startElement: function(aUri, aLocalName, aQName, aAttributes) {
+    if (aQName == "stream:stream") {
+      if ("_node" in this)
+        this._listener.onXMLError("unexpected-stream-start",
+                                  "stream:stream inside an already started stream");
+      this._node = null;
+      return;
+    }
+
+    let node = new XMLNode(this._node, aUri, aLocalName, aQName, aAttributes);
+    if (this._node)
+      this._node.addChild(node);
+
+    this._node = node;
+  },
+
+  characters: function(aCharacters) {
+    if (!this._node) {
+      // Ignore whitespace received on the stream to keep the connection alive.
+      if (aCharacters.trim()) {
+        this._listener.onXMLError("parsing-characters",
+                                  "No parent for characters: " + aCharacters);
+      }
+      return;
+    }
+
+    this._node.addText(aCharacters);
+  },
+
+  endElement: function(aUri, aLocalName, aQName) {
+    if (aQName == "stream:stream") {
+      delete this._node;
+      return;
+    }
+
+    if (!this._node) {
+      this._listener.onXMLError("parsing-node",
+                                "No parent for node : " + aLocalName);
+      return;
+    }
+
+    if (this._node.isXmppStanza()) {
+      this._listener.log("received:\n" + this._node.convertToString());
+      try {
+        this._listener.onXmppStanza(this._node);
+      } catch (e) {
+        Cu.reportError(e);
+        dump(e + "\n");
+      }
+    }
+
+    this._node = this._node._parentNode;
+  },
+
+  processingInstruction: function(aTarget, aData) { },
+  ignorableWhitespace: function(aWhitespace) { },
+  startPrefixMapping: function(aPrefix, aUri) { },
+  endPrefixMapping: function(aPrefix) { },
+
+  /* nsISAXErrorHandler implementation */
+  error: function(aLocator, aError) {
+    if (this._listener)
+      this._listener.onXMLError("parse-error", aError);
+  },
+  fatalError: function(aLocator, aError) {
+    if (this._listener)
+      this._listener.onXMLError("parse-fatal-error", aError);
+  },
+  ignorableWarning: function(aLocator, aError) {
+    if (this._listener)
+      this._listener.onXMLError("parse-warning", aError);
+  },
+
+  QueryInterface: function(aInterfaceId) {
+    if (!aInterfaceId.equals(Ci.nsISupports) &&
+        !aInterfaceId.equals(Ci.nsISAXContentHandler) &&
+        !aInterfaceId.equals(Ci.nsISAXErrorHandler))
+      throw Cr.NS_ERROR_NO_INTERFACE;
+    return this;
+  }
+};
new file mode 100644
--- /dev/null
+++ b/chat/protocols/xmpp/xmpp.js
@@ -0,0 +1,84 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Instantbird.
+ *
+ * The Initial Developer of the Original Code is
+ * Varuna JAYASIRI <vpjayasiri@gmail.com>.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Florian Quèze <florian@queze.net>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const Cu = Components.utils;
+
+Cu.import("resource:///modules/imXPCOMUtils.jsm");
+Cu.import("resource:///modules/jsProtoHelper.jsm");
+Cu.import("resource:///modules/xmpp.jsm");
+Cu.import("resource:///modules/xmpp-session.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "_", function()
+  l10nHelper("chrome://chat/locale/xmpp.properties")
+);
+
+function XMPPAccount(aProtoInstance, aImAccount) {
+  this._init(aProtoInstance, aImAccount);
+}
+XMPPAccount.prototype = XMPPAccountPrototype;
+
+function XMPPProtocol() {
+}
+XMPPProtocol.prototype = {
+  __proto__: GenericProtocolPrototype,
+  get normalizedName() "jabber",
+  get name() "XMPP (JS)",
+  get iconBaseURI() "chrome://prpl-jabber/skin/",
+  getAccount: function(aImAccount) new XMPPAccount(this, aImAccount),
+  options: {
+    resource: {get label() _("options.resource"),
+               get default() XMPPDefaultResource},
+    connection_security: {
+      get label() _("options.connectionSecurity"),
+      listValues: {
+        get require_tls() _("options.connectionSecurity.requireEncryption"),
+        get opportunistic_tls() _("options.connectionSecurity.opportunisticTLS"),
+        get allow_unencrypted_plain_auth() _("options.connectionSecurity.allowUnencryptedAuth"),
+        // "old_ssl" and "none" are also supported, but not exposed in the UI.
+        // Any unknown value will fallback to the opportunistic_tls behavior.
+      },
+      default: "opportunistic_tls"
+    },
+    server: {get label() _("options.connectServer"), default: ""},
+    port: {get label() _("options.connectPort"), default: 5222}
+  },
+  get chatHasTopic() true,
+
+  classID: Components.ID("{dde786d1-6f59-43d0-9bc8-b505a757fb30}")
+};
+
+const NSGetFactory = XPCOMUtils.generateNSGetFactory([XMPPProtocol]);
new file mode 100644
--- /dev/null
+++ b/chat/protocols/xmpp/xmpp.jsm
@@ -0,0 +1,1045 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Instantbird.
+ *
+ * The Initial Developer of the Original Code is
+ * Varuna JAYASIRI <vpjayasiri@gmail.com>.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Florian Quèze <florian@queze.net>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const EXPORTED_SYMBOLS = [
+  "XMPPConversationPrototype",
+  "XMPPMUCConversationPrototype",
+  "XMPPAccountBuddyPrototype",
+  "XMPPAccountPrototype"
+];
+
+const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
+
+Cu.import("resource:///modules/imServices.jsm");
+Cu.import("resource:///modules/imStatusUtils.jsm");
+Cu.import("resource:///modules/imXPCOMUtils.jsm");
+Cu.import("resource:///modules/jsProtoHelper.jsm");
+Cu.import("resource:///modules/socket.jsm");
+Cu.import("resource:///modules/xmpp-xml.jsm");
+Cu.import("resource:///modules/xmpp-session.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "DownloadUtils", function() {
+  Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
+  return DownloadUtils;
+});
+XPCOMUtils.defineLazyGetter(this, "FileUtils", function() {
+  Components.utils.import("resource://gre/modules/FileUtils.jsm");
+  return FileUtils;
+});
+XPCOMUtils.defineLazyGetter(this, "NetUtil", function() {
+  Components.utils.import("resource://gre/modules/NetUtil.jsm");
+  return NetUtil;
+});
+
+initLogModule("xmpp", this);
+
+XPCOMUtils.defineLazyGetter(this, "_", function()
+  l10nHelper("chrome://chat/locale/xmpp.properties")
+);
+
+/* This is an ordered list, used to determine chat buddy flags:
+ *  index < member    -> noFlags
+ *  index = member    -> voiced
+ *          moderator -> halfOp
+ *          admin     -> op
+ *          owner     -> founder
+ */
+const kRoles = ["outcast", "visitor", "participant", "member", "moderator",
+                "admin", "owner"];
+
+function MUCParticipant(aNick, aName, aStanza)
+{
+  this._jid = aName;
+  this.name = aNick;
+  this.stanza = aStanza;
+}
+MUCParticipant.prototype = {
+  __proto__: ClassInfo("prplIConvChatBuddy", "XMPP ConvChatBuddy object"),
+
+  buddy: false,
+  get alias() this.name,
+
+  role: 2, // "participant" by default
+  set stanza(aStanza) {
+    this._stanza = aStanza;
+
+    let x =
+      aStanza.getChildren("x").filter(function (c) c.uri == Stanza.NS.muc_user);
+    if (x.length == 0)
+      return;
+    x = x[0];
+    let item = x.getElement(["item"]);
+    if (!item)
+      return;
+
+    this.role = Math.max(kRoles.indexOf(item.attributes["role"]),
+                         kRoles.indexOf(item.attributes["affiliation"]));
+  },
+
+  get noFlags() this.role < kRoles.indexOf("member"),
+  get voiced() this.role == kRoles.indexOf("member"),
+  get halfOp() this.role == kRoles.indexOf("moderator"),
+  get op() this.role == kRoles.indexOf("admin"),
+  get founder() this.role == kRoles.indexOf("owner"),
+  typing: false
+};
+
+// MUC (Multi-User Chat)
+const XMPPMUCConversationPrototype = {
+  __proto__: GenericConvChatPrototype,
+
+  _init: function(aAccount, aJID, aNick) {
+    GenericConvChatPrototype._init.call(this, aAccount, aJID, aNick);
+  },
+
+  get normalizedName() this.name,
+
+  _targetResource: "",
+
+  /* Called when the user enters a chat message */
+  sendMsg: function (aMsg) {
+    let s = Stanza.message(this.name, aMsg, null, {type: "groupchat"});
+    this._account.sendStanza(s);
+  },
+
+  /* Called by the account when a presence stanza is received for this muc */
+  onPresenceStanza: function(aStanza) {
+    let from = aStanza.attributes["from"];
+    let nick = this._account._parseJID(from).resource;
+    if (aStanza.attributes["type"] == "unavailable") {
+      if (!(nick in this._participants)) {
+        WARN("received unavailable presence for an unknown MUC participant: " +
+             from);
+        return;
+      }
+      delete this._participants[nick];
+      let nickString = Cc["@mozilla.org/supports-string;1"]
+                       .createInstance(Ci.nsISupportsString);
+      nickString.data = nick;
+      this.notifyObservers(new nsSimpleEnumerator([nickString]),
+                           "chat-buddy-remove");
+      return;
+    }
+    if (!hasOwnProperty(this._participants, nick)) {
+      this._participants[nick] = new MUCParticipant(nick, from, aStanza);
+      this.notifyObservers(new nsSimpleEnumerator([this._participants[nick]]),
+                           "chat-buddy-add");
+    }
+    else {
+      this._participants[nick].stanza = aStanza;
+      this.notifyObservers(this._participants[nick], "chat-buddy-update");
+    }
+  },
+
+  /* Called by the account when a messsage is received for this muc */
+  incomingMessage: function(aMsg, aStanza, aDate) {
+    let from = this._account._parseJID(aStanza.attributes["from"]).resource;
+    let flags = {};
+    if (!from) {
+      flags.system = true;
+      from = this.name;
+    }
+    else if (from == this._nick)
+      flags.outgoing = true;
+    else
+      flags.incoming = true;
+    if (aDate) {
+      flags.time = aDate / 1000;
+      flags.delayed = true;
+    }
+    this.writeMessage(from, aMsg, flags);
+  },
+
+  getNormalizedChatBuddyName: function(aNick) this.name + "/" + aNick,
+
+  /* Called when the user closed the conversation */
+  close: function() {
+    this._account.sendStanza(Stanza.presence({to: this.name + "/" + this._nick,
+                                             type: "unavailable"}));
+    GenericConvChatPrototype.close.call(this);
+  },
+  unInit: function() {
+    this._account.removeConversation(this.name);
+    GenericConvChatPrototype.unInit.call(this);
+  }
+};
+function XMPPMUCConversation(aAccount, aJID, aNick)
+{
+  this._init(aAccount, aJID, aNick);
+}
+XMPPMUCConversation.prototype = XMPPMUCConversationPrototype;
+
+/* Helper class for buddy conversations */
+const XMPPConversationPrototype = {
+  __proto__: GenericConvIMPrototype,
+
+  _typingTimer: null,
+  _supportChatStateNotifications: true,
+  _typingState: "active",
+
+  _init: function(aAccount, aBuddy) {
+    this.buddy = aBuddy;
+    GenericConvIMPrototype._init.call(this, aAccount, aBuddy.normalizedName);
+  },
+
+  get title() this.buddy.contactDisplayName,
+  get normalizedName() this.buddy.normalizedName,
+
+  set supportChatStateNotifications(val) {
+    this._supportChatStateNotifications = val;
+  },
+
+  /* Called when the user is typing a message
+   * aLength - length of the typed message */
+  sendTyping: function(aLength) {
+    if (!this._supportChatStateNotifications)
+      return;
+
+    this._cancelTypingTimer();
+    if (aLength)
+      this._typingTimer = setTimeout(this.finishedComposing.bind(this), 10000);
+
+    this._setTypingState(aLength ? "composing" : "active");
+  },
+
+  finishedComposing: function() {
+    if (!this._supportChatStateNotifications)
+      return;
+
+    this._setTypingState("paused");
+  },
+
+  _setTypingState: function(aNewState) {
+    if (this._typingState == aNewState)
+      return;
+
+    /* to, msg, state, attrib, data */
+    let s = Stanza.message(this.to, null, aNewState);
+    this._account.sendStanza(s);
+    this._typingState = aNewState;
+  },
+  _cancelTypingTimer: function() {
+    if (this._typingTimer) {
+      clearTimeout(this._typingTimer);
+      delete this._typingTimer;
+    }
+  },
+
+  _targetResource: "",
+  get to() {
+    let to = this.buddy.userName;
+    if (this._targetResource)
+      to += "/" + this._targetResource;
+    return to;
+  },
+
+  /* Called when the user enters a chat message */
+  sendMsg: function (aMsg) {
+    this._cancelTypingTimer();
+    let cs = this._supportChatStateNotifications ? "active" : null;
+    let s = Stanza.message(this.to, aMsg, cs);
+    this._account.sendStanza(s);
+    let who;
+    if (this._account._connection)
+      who = this._account._connection._jid.jid;
+    if (!who)
+      who = this._account.name;
+    let alias = this.account.alias || this.account.statusInfo.displayName;
+    let msg = Components.classes["@mozilla.org/txttohtmlconv;1"]
+                        .getService(Ci.mozITXTToHTMLConv)
+                        .scanTXT(aMsg, Ci.mozITXTToHTMLConv.kEntities);
+    this.writeMessage(who, msg, {outgoing: true, _alias: alias});
+    delete this._typingState;
+  },
+
+  /* Called by the account when a messsage is received from the buddy */
+  incomingMessage: function(aMsg, aStanza, aDate) {
+    let from = aStanza.attributes["from"];
+    this._targetResource = this._account._parseJID(from).resource;
+    let flags = {incoming: true, _alias: this.buddy.contactDisplayName};
+    if (aDate) {
+      flags.time = aDate / 1000;
+      flags.delayed = true;
+    }
+    this.writeMessage(from, aMsg, flags);
+  },
+
+  /* Called when the user closed the conversation */
+  close: function() {
+    // TODO send the stanza indicating we have left the conversation?
+    GenericConvIMPrototype.close.call(this);
+  },
+  unInit: function() {
+    this._account.removeConversation(this.buddy.normalizedName);
+    delete this.buddy;
+    GenericConvIMPrototype.unInit.call(this);
+  }
+};
+function XMPPConversation(aAccount, aBuddy)
+{
+  this._init(aAccount, aBuddy);
+}
+XMPPConversation.prototype = XMPPConversationPrototype;
+
+/* Helper class for buddies */
+const XMPPAccountBuddyPrototype = {
+  __proto__: GenericAccountBuddyPrototype,
+
+  subscription: "none",
+  /* Returns a list of TooltipInfo objects to be displayed when the user hovers over the buddy */
+  getTooltipInfo: function() {
+    if (!this._account.connected)
+      return null;
+
+    let tooltipInfo = [];
+    if (this._resources) {
+      for (let r in this._resources) {
+        let status = this._resources[r];
+        let statusString = Status.toLabel(status.statusType);
+        if (status.statusType == Ci.imIStatusInfo.STATUS_IDLE &&
+            status.idleSince) {
+          let now = Math.floor(Date.now() / 1000);
+          let valuesAndUnits =
+            DownloadUtils.convertTimeUnits(now - status.idleSince);
+          if (!valuesAndUnits[2])
+            valuesAndUnits.splice(2, 2);
+          statusString += " (" + valuesAndUnits.join(" ") + ")";
+        }
+        if (status.statusText)
+          statusString += " - " + status.statusText;
+        let label = r ? _("tooltip.status", r) : _("tooltip.statusNoResource");
+        tooltipInfo.push(new TooltipInfo(label, statusString));
+      }
+    }
+
+    // The subscription value is interesting to display only in unusual cases.
+    if (this.subscription != "both") {
+      tooltipInfo.push(new TooltipInfo(_("tooltip.subscription"),
+                                       this.subscription));
+    }
+
+    return new nsSimpleEnumerator(tooltipInfo);
+  },
+
+  get normalizedName() this.userName,
+  /* Display name of the buddy */
+  get contactDisplayName() this.buddy.contact.displayName || this.displayName,
+
+  remove: function() {
+    if (!this._account.connected)
+      return;
+
+    let s = Stanza.iq("set", null, null,
+                      Stanza.node("query", Stanza.NS.roster, null,
+                                  Stanza.node("item", null,
+                                              {jid: this.normalizedName,
+                                               subscription: "remove"})));
+    this._account._connection.sendStanza(s);
+  },
+
+  _saveIcon: function(aPhotoNode) {
+    let type = aPhotoNode.getElement(["TYPE"]).innerText;
+    const kExt = {"image/gif": "gif", "image/jpeg": "jpg", "image/png": "png"};
+    if (!kExt.hasOwnProperty(type))
+      return;
+
+    let data = aPhotoNode.getElement(["BINVAL"]).innerText;
+    let content = atob(data.replace(/[^A-Za-z0-9\+\/\=]/g, ""));
+    let istream = Cc["@mozilla.org/io/string-input-stream;1"]
+                  .createInstance(Ci.nsIStringInputStream);
+    istream.setData(content, content.length);
+
+    let fileName = this.normalizedName + "." + kExt[type];
+    let file = FileUtils.getFile("ProfD", ["icons",
+                                           this.account.protocol.normalizedName,
+                                           this.account.normalizedName,
+                                           fileName]);
+    let ostream = FileUtils.openSafeFileOutputStream(file);
+    let buddy = this;
+    NetUtil.asyncCopy(istream, ostream, function(rc) {
+      if (Components.isSuccessCode(rc))
+        buddy.buddyIconFilename = Services.io.newFileURI(file).spec;
+    });
+  },
+
+  _preferredResource: undefined,
+  _resources: null,
+  onAccountDisconnected: function() {
+    delete this._preferredResource;
+    delete this._resources;
+  },
+  // Called by the account when a presence stanza is received for this buddy.
+  onPresenceStanza: function(aStanza) {
+    let preferred = this._preferredResource;
+
+    // Facebook chat's XMPP server doesn't send resources, let's
+    // replace undefined resources with empty resources.
+    let resource =
+      this._account._parseJID(aStanza.attributes["from"]).resource || "";
+
+    let type = aStanza.attributes["type"];
+    if (type == "unavailable") {
+      if (!this._resources || !(resource in this._resources))
+        return; // ignore for already offline resources.
+      delete this._resources[resource];
+      if (preferred == resource)
+        preferred = undefined;
+    }
+    else if (type != "unsubscribe" && type != "unsubscribed" &&
+             type != "subscribed") {
+      let statusType = Ci.imIStatusInfo.STATUS_AVAILABLE;
+      let show = aStanza.getElement(["show"]);
+      if (show) {
+        show = show.innerText;
+        if (show == "away")
+          statusType = Ci.imIStatusInfo.STATUS_AWAY;
+        else if (show == "chat")
+          statusType = Ci.imIStatusInfo.STATUS_AVAILABLE; //FIXME
+        else if (show == "dnd")
+          statusType = Ci.imIStatusInfo.STATUS_UNAVAILABLE;
+        else if (show == "xa")
+          statusType = Ci.imIStatusInfo.STATUS_IDLE;
+      }
+
+      let idleSince = 0;
+      let query = aStanza.getElement(["query"]);
+      if (query && query.uri == Stanza.NS.last) {
+        let now = Math.floor(Date.now() / 1000);
+        idleSince = now - parseInt(query.attributes["seconds"], 10);
+        statusType = Ci.imIStatusInfo.STATUS_IDLE;
+      }
+
+      let status = aStanza.getElement(["status"]);
+      status = status ? status.innerText : "";
+
+      let priority = aStanza.getElement(["priority"]);
+      priority = priority ? parseInt(priority.innerText, 10) : 0;
+
+      if (!this._resources)
+        this._resources = {};
+      this._resources[resource] = {
+        statusType: statusType,
+        statusText: status,
+        idleSince: idleSince,
+        priority: priority,
+        stanza: aStanza
+      };
+    }
+
+    for (let r in this._resources) {
+      if (preferred === undefined ||
+          this._resources[r].statusType > this._resources[preferred].statusType)
+        // FIXME also compare priorities...
+        preferred = r;
+    }
+    if (preferred != undefined && preferred == this._preferredResource &&
+        resource != preferred) {
+      // The presence information change is only for an unused resource,
+      // only potential buddy tooltips need to be refreshed.
+      this._notifyObservers("status-detail-changed");
+      return;
+    }
+
+    // Presence info has changed enough that if we are having a
+    // conversation with one resource of this buddy, we should send
+    // the next message to all resources.
+    // FIXME: the test here isn't exactly right...
+    if (this._preferredResource != preferred &&
+        this._account._conv.hasOwnProperty(this.normalizedName))
+      delete this._account._conv[this.normalizedName]._targetResource;
+
+    this._preferredResource = preferred;
+    if (preferred === undefined) {
+      let statusType = Ci.imIStatusInfo.STATUS_UNKNOWN;
+      if (type == "unavailable")
+        statusType = Ci.imIStatusInfo.STATUS_OFFLINE;
+      this.setStatus(statusType, "");
+    }
+    else {
+      preferred = this._resources[preferred];
+      this.setStatus(preferred.statusType, preferred.statusText);
+    }
+  },
+
+  /* Can send messages to buddies who appear offline */
+  get canSendMessage() this.account.connected,
+
+  /* Called when the user wants to chat with the buddy */
+  createConversation: function()
+    this._account.createConversation(this.normalizedName)
+};
+function XMPPAccountBuddy(aAccount, aBuddy, aTag, aUserName)
+{
+  this._init(aAccount, aBuddy, aTag, aUserName);
+}
+XMPPAccountBuddy.prototype = XMPPAccountBuddyPrototype;
+
+/* Helper class for account */
+const XMPPAccountPrototype = {
+  __proto__: GenericAccountPrototype,
+
+  _jid: null, // parsed Jabber ID: node, domain, resource
+  _connection: null, // XMPP Connection
+
+  _init: function(aProtoInstance, aImAccount) {
+    GenericAccountPrototype._init.call(this, aProtoInstance, aImAccount);
+
+    /* Ongoing conversations */
+    this._conv = {};
+    this._buddies = {};
+    this._mucs = {};
+    this._pendingAuthRequests = [];
+  },
+
+  get canJoinChat() true,
+  chatRoomFields: {
+    room: {get label() _("chatRoomField.room"), required: true},
+    server: {get label() _("chatRoomField.server"), required: true},
+    nick: {get label() _("chatRoomField.nick"), required: true},
+    password: {get label() _("chatRoomField.password"), isPassword: true}
+  },
+  parseDefaultChatName: function(aDefaultChatName) {
+    if (!aDefaultChatName)
+      return {nick: this._jid.node};
+
+    let jid = this._parseJID(aDefaultChatName);
+    return {
+      room: jid.node,
+      server: jid.domain,
+      nick: jid.resource || this._jid.node
+    };
+  },
+  getChatRoomDefaultFieldValues: function(aDefaultChatName) {
+    let rv = GenericAccountPrototype.getChatRoomDefaultFieldValues
+                                    .call(this, aDefaultChatName);
+    if (!rv.values.nick)
+      rv.values.nick = this._jid.node;
+
+    return rv;
+  },
+  joinChat: function(aComponents) {
+    let jid =
+      aComponents.getValue("room") + "@" + aComponents.getValue("server");
+    let nick = aComponents.getValue("nick");
+    if (jid in this._mucs)
+      return; // FIXME, check if we need to rejoin
+
+    let x;
+    let password = aComponents.getValue("password");
+    if (password) {
+      x = Stanza.node("x", Stanza.NS.muc, null,
+                      Stanza.node("password", null, null, password));
+    }
+    this._mucs[jid] = nick;
+    this._connection.sendStanza(Stanza.presence({to: jid + "/" + nick}, x));
+  },
+
+  get normalizedName() this._normalizeJID(this.name),
+
+  _idleSince: 0,
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic == "idle-time-changed") {
+      let idleTime = parseInt(aData, 10);
+      if (idleTime)
+        this._idleSince = Math.floor(Date.now() / 1000) - idleTime;
+      else
+        delete this._idleSince;
+      this._shouldSendPresenceForIdlenessChange = true;
+      executeSoon((function() {
+        if ("_shouldSendPresenceForIdlenessChange" in this)
+          this._sendPresence();
+      }).bind(this));
+    }
+
+    if (aTopic != "status-changed")
+      return;
+
+    this._sendPresence();
+  },
+
+  /* GenericAccountPrototype events */
+  /* Connect to the server */
+  connect: function() {
+    this._jid = this._parseJID(this.name);
+
+    // For the resource, if the user has edited the option to a non
+    // empty value, use that.
+    if (this.prefs.prefHasUserValue("resource")) {
+      let resource = this.getString("resource");
+      if (resource)
+        this._jid.resource = resource;
+    }
+    // Otherwise, if the username doesn't contain a resource, use the
+    // value of the resource option (it will be the default value).
+    // If we set an empty resource, XMPPSession will fallback to
+    // XMPPDefaultResource (set to brandShortName).
+    if (!this._jid.resource)
+      this._jid.resource = this.getString("resource");
+
+    //FIXME if we have changed this._jid.resource, then this._jid.jid
+    // needs to be updated. This value is however never used because
+    // while connected it's the jid of the session that's interesting.
+
+    this._connection =
+      new XMPPSession(this.getString("server") || this._jid.domain,
+                      this.getInt("port") || 5222,
+                      this.getString("connection_security"), this._jid,
+                      this.imAccount.password, this);
+  },
+
+  unInit: function() {
+    if (this._connection)
+      this._disconnect(undefined, undefined, true);
+    delete this._jid;
+    delete this._conv;
+    delete this._buddies;
+    delete this._mucs;
+  },
+
+  /* Disconnect from the server */
+  disconnect: function() {
+    this._disconnect();
+  },
+
+  addBuddy: function(aTag, aName) {
+    if (!this._connection)
+      throw "The account isn't connected";
+
+    let jid = this._normalizeJID(aName);
+    if (!jid || jid.indexOf("@") == -1)
+      throw "Invalid username";
+
+    if (this._buddies.hasOwnProperty(jid)) {
+      let subscription = this._buddies[jid].subscription;
+      if (subscription && (subscription == "both" || subscription == "to")) {
+        DEBUG("not re-adding an existing buddy");
+        return;
+      }
+    }
+    else {
+      let s = Stanza.iq("set", null, null,
+                        Stanza.node("query", Stanza.NS.roster, null,
+                                    Stanza.node("item", null, {jid: jid},
+                                                Stanza.node("group", null, null,
+                                                            aTag.name))));
+      this._connection.sendStanza(s);
+    }
+    this._connection.sendStanza(Stanza.presence({to: jid, type: "subscribe"}));
+  },
+
+  /* Loads a buddy from the local storage.
+   * Called for each buddy locally stored before connecting
+   * to the server. */
+  loadBuddy: function(aBuddy, aTag) {
+    let buddy = new this._accountBuddyConstructor(this, aBuddy, aTag);
+    this._buddies[buddy.normalizedName] = buddy;
+    return buddy;
+  },
+
+  /* XMPPSession events */
+  /* Called when the XMPP session is started */
+  onConnection: function() {
+    this.reportConnecting(_("connection.downloadingRoster"));
+    let s = Stanza.iq("get", null, null, Stanza.node("query", Stanza.NS.roster));
+
+    /* Set the call back onRoster */
+    this._connection.sendStanza(s, this.onRoster, this);
+  },
+
+
+  /* Called whenever a stanza is received */
+  onXmppStanza: function(aStanza) {
+  },
+
+  /* Called when a iq stanza is received */
+  onIQStanza: function(aStanza, aHandled) {
+    if (aHandled)
+      return;
+
+    if (aStanza.attributes["type"] == "set") {
+      for each (let qe in aStanza.getChildren("query")) {
+        if (qe.uri != Stanza.NS.roster)
+          continue;
+
+        for each (let item in qe.getChildren("item")) {
+          this._onRosterItem(item, true);
+          let jid = item.attributes["jid"];
+        }
+        return;
+      }
+    }
+
+    if (aStanza.attributes["from"] == this._jid.domain) {
+      let ping = aStanza.getElement(["ping"]);
+      if (ping && ping.uri == Stanza.NS.ping) {
+        let s = Stanza.iq("result", aStanza.attributes["id"], this._jid.domain);
+        this._connection.sendStanza(s);
+      }
+    }
+  },
+
+  /* Called when a presence stanza is received */
+  onPresenceStanza: function(aStanza) {
+    let from = aStanza.attributes["from"];
+    DEBUG("Received presence stanza for " + from);
+
+    let jid = this._normalizeJID(from);
+    if (jid in this._buddies)
+      this._buddies[jid].onPresenceStanza(aStanza);
+    else if (jid in this._mucs) {
+      if (typeof(this._mucs[jid]) == "string") {
+        // We have attempted to join, but not created the conversation yet.
+        if (aStanza.attributes["type"] == "error") {
+          delete this._mucs[jid];
+          ERROR("Failed to join MUC: " + aStanza.convertToString());
+          return;
+        }
+        let nick = this._mucs[jid];
+        this._mucs[jid] = new this._MUCConversationConstructor(this, jid, nick);
+      }
+      this._mucs[jid].onPresenceStanza(aStanza);
+    }
+    else if (aStanza.attributes["type"] == "subscribe") {
+      let authRequest = {
+        _account: this,
+        get account() this._account.imAccount,
+        userName: jid,
+        _sendReply: function(aReply) {
+          let connection = this._account._connection;
+          if (!connection)
+            return;
+          this._account._pendingAuthRequests =
+            this._account._pendingAuthRequests.filter(function(r) r !== this);
+          connection.sendStanza(Stanza.presence({to: this.userName,
+                                                 type: aReply}));
+        },
+        grant: function() { this._sendReply("subscribed"); },
+        deny: function() { this._sendReply("unsubscribed"); },
+        cancel: function() {
+          Services.obs.notifyObservers(this,
+                                       "buddy-authorization-request-canceled",
+                                       null);
+        },
+        QueryInterface: XPCOMUtils.generateQI([Ci.prplIBuddyRequest])
+      };
+      Services.obs.notifyObservers(authRequest, "buddy-authorization-request",
+                                   null);
+      this._pendingAuthRequests.push(authRequest);
+    }
+    else if (from != this._connection._jid.jid)
+      WARN("received presence stanza for unknown buddy " + from);
+  },
+
+  /* Called when a message stanza is received */
+  onMessageStanza: function(aStanza) {
+    let norm = this._normalizeJID(aStanza.attributes["from"]);
+
+    let body;
+    let b = aStanza.getElement(["body"]);
+    if (b)
+      body = b.getXML();
+    if (body) {
+      let date;
+      let delay = aStanza.getElement(["delay"]);
+      if (delay && delay.uri == Stanza.NS.delay) {
+        if (delay.attributes["stamp"])
+          date = new Date(delay.attributes["stamp"]);
+      }
+      if (date && isNaN(date))
+        date = undefined;
+      if (aStanza.attributes["type"] == "groupchat") {
+        if (!this._mucs.hasOwnProperty(norm)) {
+          WARN("Received a groupchat message for unknown MUC " + norm);
+          return;
+        }
+        this._mucs[norm].incomingMessage(body, aStanza, date);
+        return;
+      }
+
+      if (!this.createConversation(norm))
+        return;
+      this._conv[norm].incomingMessage(body, aStanza, date);
+    }
+
+    // Don't create a conversation to only display the typing notifications.
+    if (!this._conv.hasOwnProperty(norm))
+      return;
+
+    let state;
+    let s = aStanza.getChildrenByNS(Stanza.NS.chatstates);
+    if (s.length > 0)
+      state = s[0].localName;
+    if (state) {
+      DEBUG(state);
+      if (state == "active")
+        this._conv[norm].updateTyping(Ci.prplIConvIM.NOT_TYPING);
+      else if (state == "composing")
+        this._conv[norm].updateTyping(Ci.prplIConvIM.TYPING);
+      else if (state == "paused")
+        this._conv[norm].updateTyping(Ci.prplIConvIM.TYPED);
+    }
+    else
+      this._conv[norm].supportChatStateNotifications = false;
+  },
+
+  /* Called when there is an error in the xmpp session */
+  onError: function(aError, aException) {
+    if (aError === null || aError === undefined)
+      aError = Ci.prplIAccount.ERROR_OTHER_ERROR;
+    this._disconnect(aError, aException.toString());
+  },
+
+  /* Callbacks for Query stanzas */
+  /* When a vCard is recieved */
+  onVCard: function(aStanza) {
+    let jid = this._normalizeJID(aStanza.attributes["from"]);
+    if (!jid || !this._buddies.hasOwnProperty(jid))
+      return;
+    let buddy = this._buddies[jid];
+
+    let vCard = aStanza.getElement(["vCard"]);
+    if (!vCard)
+      return;
+
+    for each (let c in vCard.children) {
+      if (c.type != "node")
+        continue;
+      if (c.localName == "FN")
+        buddy.serverAlias = c.innerText;
+      if (c.localName == "PHOTO")
+        buddy._saveIcon(c);
+    }
+  },
+
+  _normalizeJID: function(aJID) {
+    let slashIndex = aJID.indexOf("/");
+    if (slashIndex != -1)
+      aJID = aJID.substr(0, slashIndex);
+    return aJID.toLowerCase();
+  },
+
+  _parseJID: function(aJid) {
+    let match =
+      /^(?:([^@/<>'\"]+)@)?([^@/<>'\"]+)(?:\/([^<>'\"]*))?$/.exec(aJid);
+    if (!match)
+      return null;
+
+    return {jid: match[0], node: match[1], domain: match[2],
+            resource: match[3]};
+  },
+
+  _onRosterItem: function(aItem, aNotifyOfUpdates) {
+    let jid = aItem.attributes["jid"];
+    if (!jid) {
+      WARN("Received a roster item without jid: " + aItem.getXML());
+      return "";
+    }
+    jid = this._normalizeJID(jid);
+
+    let subscription =  "";
+    if ("subscription" in aItem.attributes)
+      subscription = aItem.attributes["subscription"];
+    if (subscription == "both" || subscription == "to") {
+      let s = Stanza.iq("get", null, jid,
+                        Stanza.node("vCard", Stanza.NS.vcard));
+      this._connection.sendStanza(s, this.onVCard, this);
+    }
+    else if (subscription == "remove") {
+      this._forgetRosterItem(jid);
+      return "";
+    }
+
+    let buddy;
+    if (this._buddies.hasOwnProperty(jid))
+      buddy = this._buddies[jid];
+    else {
+      let tagName = _("defaultGroup");
+      for each (let group in aItem.getChildren("group")) {
+        let name = group.innerText;
+        if (name) {
+          tagName = name;
+          break; // TODO we should create an accountBuddy per group,
+                 // but this._buddies would probably not like that...
+        }
+      }
+      let tag = Services.tags.createTag(tagName);
+      buddy = new this._accountBuddyConstructor(this, null, tag, jid);
+    }
+
+    let alias = "name" in aItem.attributes ? aItem.attributes["name"] : "";
+    if (alias) {
+      if (aNotifyOfUpdates && this._buddies.hasOwnProperty(jid))
+        buddy.serverAlias = alias;
+      else
+        buddy._serverAlias = alias;
+    }
+    if (subscription)
+      buddy.subscription = subscription;
+    if (!this._buddies.hasOwnProperty(jid)) {
+      this._buddies[jid] = buddy;
+      Services.contacts.accountBuddyAdded(buddy);
+    }
+    else if (aNotifyOfUpdates)
+      buddy._notifyObservers("status-detail-changed");
+    return jid;
+  },
+  _forgetRosterItem: function(aJID) {
+    Services.contacts.accountBuddyRemoved(this._buddies[aJID]);
+    delete this._buddies[aJID];
+  },
+
+  /* When the roster is received */
+  onRoster: function(aStanza) {
+    for each (let qe in aStanza.getChildren("query")) {
+      if (qe.uri != Stanza.NS.roster)
+        continue;
+
+      let savedRoster = Object.keys(this._buddies);
+      let newRoster = {};
+      for each (let item in qe.getChildren("item")) {
+        let jid = this._onRosterItem(item);
+        if (jid)
+          newRoster[jid] = true;
+      }
+      for each (let jid in savedRoster) {
+        if (!hasOwnProperty(newRoster, jid))
+          this._forgetRosterItem(jid);
+      }
+      break;
+    }
+
+    this._sendPresence();
+    for each (let b in this._buddies) {
+      if (b.subscription == "both" || b.subscription == "to")
+        b.setStatus(Ci.imIStatusInfo.STATUS_OFFLINE, "");
+    }
+    this.reportConnected();
+  },
+
+  /* Public methods */
+  /* Send a stanza to a buddy */
+  sendStanza: function(aStanza) {
+    this._connection.sendStanza(aStanza);
+  },
+
+  // Variations of the XMPP protocol can change these default constructors:
+  _conversationConstructor: XMPPConversation,
+  _MUCConversationConstructor: XMPPMUCConversation,
+  _accountBuddyConstructor: XMPPAccountBuddy,
+
+  /* Create a new conversation */
+  createConversation: function(aNormalizedName) {
+    if (!this._buddies.hasOwnProperty(aNormalizedName)) {
+      ERROR("Trying to create a conversation; buddy not present: " + aNormalizedName);
+      return null;
+    }
+
+    if (!this._conv.hasOwnProperty(aNormalizedName)) {
+      this._conv[aNormalizedName] =
+        new this._conversationConstructor(this, this._buddies[aNormalizedName]);
+    }
+
+    return this._conv[aNormalizedName];
+  },
+
+  /* Remove an existing conversation */
+  removeConversation: function(aNormalizedName) {
+    if (aNormalizedName in this._conv)
+      delete this._conv[aNormalizedName];
+    else if (aNormalizedName in this._mucs)
+      delete this._mucs[aNormalizedName];
+  },
+
+  /* Private methods */
+
+  /* Disconnect from the server */
+  /* The aError and aErrorMessage parameters are passed to reportDisconnecting
+   * and used by the account manager.
+   * The aQuiet parameter is to avoid sending status change notifications
+   * during the uninitialization of the account. */
+  _disconnect: function(aError, aErrorMessage, aQuiet) {
+    if (!this._connection)
+      return;
+
+    if (aError === undefined)
+      aError = Ci.prplIAccount.NO_ERROR;
+    this.reportDisconnecting(aError, aErrorMessage);
+
+    for each (let b in this._buddies) {
+      if (!aQuiet)
+        b.setStatus(Ci.imIStatusInfo.STATUS_UNKNOWN, "");
+      b.onAccountDisconnected();
+    }
+
+    for each (let request in this._pendingAuthRequests)
+      request.cancel();
+    this._pendingAuthRequests = [];
+
+    this._connection.disconnect();
+    delete this._connection;
+
+    this.reportDisconnected();
+  },
+
+  /* Set the user status on the server */
+  _sendPresence: function() {
+    delete this._shouldSendPresenceForIdlenessChange;
+
+    if (!this._connection)
+      return;
+
+    let si = this.imAccount.statusInfo;
+    let statusType = si.statusType;
+    let show = "";
+    if (statusType == Ci.imIStatusInfo.STATUS_UNAVAILABLE)
+      show = "dnd";
+    else if (statusType == Ci.imIStatusInfo.STATUS_AWAY ||
+             statusType == Ci.imIStatusInfo.STATUS_IDLE)
+      show = "away";
+    let children = [];
+    if (show)
+      children.push(Stanza.node("show", null, null, show));
+    let statusText = si.statusText;
+    if (statusText)
+      children.push(Stanza.node("status", null, null, statusText));
+    if (this._idleSince) {
+      let time = Math.floor(Date.now() / 1000) - this._idleSince;
+      children.push(Stanza.node("query", Stanza.NS.last, {seconds: time}));
+    }
+    this._connection.sendStanza(Stanza.presence({"xml:lang": "en"}, children));
+  }
+};
new file mode 100644
--- /dev/null
+++ b/chat/protocols/xmpp/xmpp.manifest
@@ -0,0 +1,3 @@
+component {dde786d1-6f59-43d0-9bc8-b505a757fb30} xmpp.js
+contract @mozilla.org/chat/xmpp;1 {dde786d1-6f59-43d0-9bc8-b505a757fb30}
+#category im-protocol-plugin prpl-jabber @mozilla.org/chat/xmpp;1
new file mode 100644
--- /dev/null
+++ b/chat/themes/Makefile.in
@@ -0,0 +1,44 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is the Mozilla Browser code.
+#
+# The Initial Developer of the Original Code is
+#   Florian QUEZE <florian@instantbird.org>
+# Portions created by the Initial Developer are Copyright (C) 2008
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH		= ../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..bcca43a9178551ed4eeb6031dba441f03a85a9f9
GIT binary patch
literal 595
zc$@)K0<8UsP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10nkZAK~y-6rIS5tT2UOvf9HBlxRIJFK}sb<mQWC+U?FtQCWS05
zp_x0^tpPtkz|k*Z$1bhVscVNwT>^>Hp^^-0$%`gj&f7g5qDGC$(hJXU&w1X?;rxYj
zj_FVAi%nn;D2xEm7@_7Wt~J9S8;E@&fKq%Zevp5i4>Ic+($CZANYg#-(*4t=eb^4I
zwq>B|Dz`%6oEwY3W;e3M?YG;kJY7LsjgD?%B7?S?tMe<G-<n*UUetj%uHu|GYEsH>
zWQ#i=b_iAjlvOj3RwAt=_9RCBU7ln%Sp-S|f=_J5m*UOckGnyz8cb3bH5mV2UtV)m
zI|?Hmz4U;++*U4FPA(&@yq7*{5Xs5DNC;B7Q~<p9fx_DQ8p^7>^l`E^vujf={EQFq
z$_EO`XGv6~W(f0wc|$Ml0Tg@yWfhT)9u>?RDysnSfJX1E_wd=RTzEHOKLluaK%?L3
zKT4iAlUAa-3gFNKYMo!5aBx0&m^^PbkeId!fzO_+xR%wneEj_wX&w+v8yboFslZTn
z71tWS#Om;DSU>r4!p-H)9XXmcltYRBp`lM5s4g5P;fsUBi$oC1#2^JGGN``_(=)i=
hF2rzunEq#+{{bT+1EE9c1$F=c002ovPDHLkV1lnt|KI=s
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..4709124366a6d62986e1d865369c9b40f3315d4e
GIT binary patch
literal 412
zc%17D@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf<Z~8yL>2?p
zUk71ECym(^Ktah8*NBqf{Irtt#G+J&^73-M%)IR4<ivthz5Jr|+3#$m7#J8OJzX3_
zEPCHgI_q`VL8Ntmr{fU^PLW4mHQfQ;cf0v7EH8-G+MyTmNzAVOfm4AXS5kTGi)C+_
zP5g|0&MjQ1D_Xeg#kIzV2U6da>p$-?IJV(@;kQ@+*cl$Yt5RrN#Ld(2VYdkLM9JCd
z42j)=JV$%4e{$QqdfsKb?BFY`pSN3HZeMvWwJ!VEvv>Xt5?$-Af13B}dTqP%iPtir
z+Mle{e|pTg<6km!nz$K1+uYNAi}_76XV%@-at)hwNtg2+GrPnM8;-xh@{uRyFJIU4
zV{ynZHC%0Urtv3hz*Z$~;{$TZdn)2{uHCz~#cQHBr@(E7i*E{-g`dp2yrs=UQ!|c>
z*Tp)yr2p<j`ComUJg&hDrgc5rdQszzVV>g}`Io|<C+vT0pQcp?3`qu0S3j3^P6<r_
DCLE^}
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..efda0de255f50f5fcac04e93d6f79630256df24d
GIT binary patch
literal 559
zc$@(*0?_@5P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10jx<xK~y-6rIRsBD?t>6zZn*nWh5Hd6bd33FztgpY_$j$!FCI6
z{Q;?L(%9MiCls_5i*z;?(ZB;M0|{cKxEK(2XWeWZi%m3|RUbHTnz{GPIdi#37-JYe
z$3iRvTR?dTfYuNPo-yt5eldFBSO@{sEZg2IRw|CWxJZ6}9^#l@w~OEH(z&?MNxxqQ
z_Prrd7-L4{6Sq{Vu553kayfW@o<MMaPqWd$zq>mHHaugD96G7FrBZcmX9qPs4O+h=
z4Bw!prdX>~a0`VhPy-N0ux#1(zxCZ+N9A%5MVaa!oIF0#I6KqHVDLu*TgBB?M=6ym
zjjLz4APiALP@J1{fK3Z1y9*0x<4EN{T)4K)HT<=Ja=uVVANY>=sg{=#plkstc`*2q
zG*JU2(CXdXWdHY@dGwy109q1g`M0-UlRgb_bnj~oa4CU<&efF;9v-rZ87_h#pcBU$
zIFz0-?IcO+&GU0i5M&d_9GEbqsWl1IJ!9G*S9xZpy0Wx{g3KWXQAD%f$A`C7o(z+e
xd&SvV$F(f-kcKIW@Z*@y%ZpB6GEBdT^D9s#il_{R7f%2H002ovPDHLkV1m(+<N5#q
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..485997193bb2c961619ef95518095159dc163117
GIT binary patch
literal 403
zc%17D@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf<Z~8yL>2?p
zUk71ECym(^Ktah8*NBqf{Irtt#G+J&^73-M%)IR4<ivthz5Jr|+3#$m7#J7@JY5_^
zEPCHg+S_#~K%jNMbW_qyx7jy-Flg$w=zeF^Iik+VB6hm9bx-Fs&FguItfxhivZep{
zAG)eEQ}>Uf!1~s=d_3%W#pi72NUAFB3A*O}rk;`EO**skQmY33E$97%72b6IVbZuP
z<?h95di>{?U$v{>?V9jhWwYeRikMe6|6g?0*osJKUR`N<?)>kBgwUx?CapTnCQo<7
ze3ErtYA18gNAgG9_UzDZCNt?9dFvytO0B=TiQ)bf$KCfb=6FoMf1dAz(W~c@#T~ov
za^}B@FWk?5CVP(B=bv-`a;vQtJ#a-u;Mio(N}oyAp9=S?UE9Ut<nknuU$H}LYSP7=
uuCmyReahUor*U*CvM43(W3H(4sMnppK5JpRp*t`D89ZJ6T-G@yGywq0m7ih&
new file mode 100644
--- /dev/null
+++ b/chat/themes/browserRequest.css
@@ -0,0 +1,69 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is oauthorizer.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Shane Caraveo <shanec@mozillamessaging.com>
+ *   Florian Quèze <florian@instantbird.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+statusbar {
+  font-weight: bold;
+  height: 2em;
+}
+#security-button {
+  width: 20px;
+  padding: 0;
+  margin: 0;
+}
+#security-button[level="high"],
+#security-button[level="low"] {
+  background-image: url("chrome://chat/skin/icons/secure.png");
+}
+
+#security-button[level="broken"] {
+  background-image: url("chrome://chat/skin/icons/insecure.png");
+}
+
+#header {
+  padding: 5px;
+  border-bottom: 1px solid black;
+}
+
+#header label {
+  font-weight: bold;
+  font-size: 1.4em;
+}
+
+#header description {
+  padding: 6px;
+  font-weight: bold;
+}
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..6c88abed99ade8cb55149870dff143753dffadf1
GIT binary patch
literal 520
zc$@(U0{8uiP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10fk9KK~y-6m6JV=;y@6EKiiFPwv!O;30ef~-eHA+2+08?8^aNd
zKv)O@-(#f}8+6EzW)CnD5-}%OO*+yfmfh~2V8)0s9<=kMmvmLVdR6LjthHQXu~=-A
zB<XSkzVGj%DCz-Vt$oKZ3~iQWHqY})W?5##FtkO!_^699=9Y;wM3v?LEd~%tl605L
zC9dlNFr7|6Cs5Tj&-2d-lsjsz&yLC{awn6C^?iSLB_O3Fj^pc2LeOrv*>1Nqn@t9T
z0rUC1E6SFGAh;@CtyYZ3W7g|60K?(%9CHiH#+XWKtpR8>8jMCGTCLVO1|aJ7`Yw*+
zZm-v~`~BV+W4y8hDJ8}jgb?)m{dXff0AUzfA;iDa>GV@6b$WYyoXuvHi@Y4owbo*c
z`F%JXzAL33fTuU!94*VTR|n-|DiB4{-!x5sl;1ti)16Mo+iW&Ytya7LP{45<fG<G3
zST;Zq1X4=*OKbf#j^n%P-@@ya0x9r(G1ik(>fv}ie$!h20n!)lnhXll#!R^Y0000<
KMNUMnLSTYnM&A+u
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e4f2f1dfc999c1e0ccaf32ad9b454ccdf21112fc
GIT binary patch
literal 556
zc$@(&0@MA8P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00001b5ch_0Itp)
z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01ejw01ejxLMWSf00007bV*G`2ipM-
z4ht4^ELmRw00FE?L_t(I%au~iisC>RtnLUZ=mX>l#N%ED83@XTJ(#!fAqG0=A>tto
z`yRU|WluZ7>xwIR1HA>~ddXq%U^;2jy_YedtFto&1z-PsRn<QPSMWUVrx2o#H&Cn9
zo;}ZVZZ0y$w3Jd8MRAi-O3fJ4=lzv?E=no92bEiq-`flT6(K}_GMRu<3IG@mhaVHT
z<ysWQ#{{k&Pp4CvD2`*SRx9lHdjNoGnr{oV+ih^p%W<>Wgl*ffZ5w%>BZ?yIc00JP
z%Y5JGgfXT^qtQRn;yA`^Hp5^rfMFQO^StEYa6k|QDos<1o+B%zF1TDS(d+d<2!Rly
zWSXYXTBFr!Q2<YsYPI^zIqy4;qk|w&N-5)<!m=y~Axh^g%gVaZXaK+i9goKj0L<s}
zKkN1S3+LSAoSOiUW!Z)EbqxUY^%B2@Vfbk}o&E;!0^o%Z@*|3(yUj_G06<9Yy|3%K
zkM(-}SGU`x04|PjJRY&xY?PGJGVTd17K?4C)5*8n?Pp3U(OM%-QzS`(D2kMna_Ia1
uck;iV=DO}9fF}SCuTvp_ufFdu0pKs4pgmWHle#1T0000<MNUMnLSTYr)A1Mp
new file mode 100644
--- /dev/null
+++ b/chat/themes/conv.css
@@ -0,0 +1,64 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2007.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+.ib-msg-txt {
+  white-space: pre-wrap;
+  word-wrap: break-word;
+}
+
+.ib-img-smile {
+  vertical-align: text-bottom;
+}
+
+*:-moz-any-link .ib-img-smile {
+  border: none;
+  border-bottom: solid 1px;
+  margin-bottom: 1px;
+}
+
+.moz-txt-underscore {
+  text-decoration: underline;
+}
+
+/* disable overflow in themes until bug 42676 is fixed in Mozilla */
+#Chat {
+  overflow: hidden !important;
+}
+
+#Chat * {
+  overflow: visible !important;
+}
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1960f1f10d3e252abfb759ef231f0651e75c7e46
GIT binary patch
literal 1072
zc$@(+1kd}4P)<h;3K|Lk000e1NJLTq000&M000&U1^@s6#I$TX00009a7bBm000XU
z000XU0RWnu7ytkR;z>k7R5*>*lv`+ARTPH5ea@LPlbLinX_8)II$=^%X(l=vDb}R+
zLi1onZ7HTo!OM$<q88s2MZ+Kng5Z-y(FZ~Cl87RfVzrII*c73oy`<AX%xyx`4AZ1Z
zW|Ew<&&=6AOw%@P#QN9;KWno#e0%M+*4}WZnfQ-=Jsb`Pg%Cc#7x4=3kgaUMQc78=
zRB8q&{4a2QeSK-EYgK()HQQRIv)vRbW@=P81u{~IGZ}}@ed*GUX}sgfWHNVK;7BC0
z>XTagzAZ-nknwO+joh=@Bdg1?%a-Ap%Hg{>f#=96Cx7ht;IGnn`M`+x+|bZadeK#H
zhHiUdcj&W{p_Wai`*q>1gR5rtZPk?mV{#UAG=n}b$(Q<=Yl-PbELw5T$rG(xLvCVV
z$Ih|AMABXoy#M3<o=w(V?c?LA_QLzGFPGs8{JkkW6It|}jV|-(vliw=7ISn0CtQiz
z{E%nW(e7=dk?{9*&pk4gIGk_+v>U;4+9*HJ+w+E}b@vLnrVRg?Vf5@gy5*qT63vq6
zwu5Fn7_%0E#NQNFlwv&bSoy1Gx?hviM)?A35!kmj=<_M{q_wkA^Pe3?pR>@dB2cp=
zn(ZKL2W$tV19LY1Urr-7*6Du5F5lXq?-p>IYBQegA6cU<bG@$5L{DFVc^fQQl<gG#
zyoB@>INc9je?V6^2!ZBN{?>u9m2Ilc05Gnlo2W9^jsCoWng{ZM3n_!X6a)j{Gr?6?
zp*CbPAZ>69MQIvL&OmLn5FP1n_$|@g3*@hZv!<&;%d<>g2FHbw0niQ5^r9CU2m#71
z#>CZ0bpg|a*__IUDrd-=H#M(oSPtxJN-~!65(<FnFTynas@Fi*!SEE<3wve>1pRO^
zWuAVj={#M-auGNlkGmHdYo}dBn)DRZuP<h`AWd7Wh4_X#NMFI#)Yzq1BpZ*%ZymQ>
zNg!w69eqcAb6B!*Jp@D7OR$uJLD*P_>Np~E>tpZcN&?rO_M#`PloB1BtlG1C*B9F6
z9a}|HRPg&*7##z{xS_5t+_f5-??v^TS8jLb@s1z9d;S@kF9{*kEnt9N)oYfX`YBrd
z_IqDxb@dU^y4_?>!sQvr&KB!g5dy!7YVVY)KRMX{;hRtABQ1RmqCqa1@q+cJo)QzY
zGWOz22lY#XuTbBp2(R{v%H^ocG^ECDl79+kb<GFoKK<yBL}r>q)BKH$fBiPwRl8nG
ztZd!IW7UDryY3f#eGh8c@hG9{UKOcLOzeO3uhErT#(w0>$+##s?6$y*4`}x4NOOt_
qBSd+=fGGz#Veb`tm;B`KO#26xOr*^y81y0l0000<MNUMnLSTaBS`jJ$
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e798681e9819fdc7e092fc5cf96dc1f3950264e6
GIT binary patch
literal 622
zc$@)l0+IcRP)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F800004b3#c}2nYxW
zd<bNS00009a7bBm000R(000R(0q|s!N&o-=8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10qaRbK~z|U?U%8N8bKJxe>09Im#`5Jvni4;_y(!+78ZGg+zW_j
zb#h>7<=H6-RsoxYR5oHGHfcf-ggiln6$~-E8rR(~g}bMuiX5lS{^4U;nBCuR=9^+h
zi3q2RJ}EsB01QC6Tz;I(<*xGid@-F)tNR4{zAv-c?9+Tcuhr{y*8t>lx$?!u#ieCg
z_X>}*SS&7Et=0;-HUPG57cI+D%jNPId@RdSwrv-IYYkwGNgai}mza6-O_&HI0*OE(
zkO(9KiGT+1Jn#3h!7<YSIF55OnM~S+Lg7(7K&kEN%=di&j4>EvZr}e+JpO+&nM^##
zac%%qd@-q1DzCEH>|3q%LlIG%&E|EfRC>8yugPRGxUL&?I-PHx=Y0jxdfPn_(UE}K
ze-f>(h@g}bfK)0)7>0iw$9dOiG`@(45EqaJ&VVPtd97Ca;JU7?Rx25g$6mEseGj|<
zo*!7xqIwFPNB=(q(ju~LL1e8WYYn8rFw|OW27|%RUaxo6Y&JhfV-7tzE8qbT_b!YW
zN-4D!h$4Fx0ztdo{xKSj-gLX&?*~Hn6JI{VsP;Kq0TB_URKRX_;dDCvI2;bk{eJ(~
z9pG^S_9g~wfIvicn-Feya*ACm;t%f<75x)-s}RIob?WQ+U+9I*vsErz;Q#;t07*qo
IM6N<$f^qW)?*IS*
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..3fc98a6e34afaa6d63ceb7685ffda21d6289890e
GIT binary patch
literal 992
zc$@*?10Vc}P)<h;3K|Lk000e1NJLTq001xm001xu1^@s6R|5Hm00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H116@f(K~!jg?U^xXBS#d5|2Mlkma?pipu(KN0Uf9?q)3-^!IcZw
z;ULAOa3x$_Ascd_xU&UrjB#PGapA&5aA}Oe;Ko&&b8!TMBk4c^5y`>9ifm+MrQMez
zJMu{OIR`m=a>xF#?950z^MCvH&A<#XGu~$j(+2*lVgS>M0Zhx*EA#X7kEE1yQpzt(
z8M;fp@Birg{@U{L@^7k*)HJ%Vu<%tXmD-+}nQ_L(#_TutVo;4nqqn!W*F8Hs`(|lr
z>3cwnKq8UIXS3O)ZQJN}yXIWpp>#TJXR}$mP$=X9Tm$5CxyKU|6X`@EfmW+!)Z|_2
zcDooI9YrdYO6PL9$Av=SwEzIeaXioSdhf%k%*^mSuje?92YdjtPz+#NF@R~s0HzfK
zm{ts6S}}lW#Q>%i1DI9}U|KPNX~h7h^<e-=DXX<w%^@NXk!eqFl=4@r)f_2h6^OPL
z^Z9%;pU=N2m&;EwnG6L%aO2@H4AJRy5QgDEyOa`A$_KCySeAuSsl>kTzgS#cYy#Tm
zuJ8NLcXxL`_B`*)OeWJCj>P2TWO96bT(sNmep@n`1QB6>fB&l8ZuhB$5K!Bcq;;f<
zdZdX6LJWS%!)@xnrBcamwOU8M??3PVrtgOmk(isC`^2)WPnns5AP~$<BO@c9O-)Tb
zo1UKj=<@OsL<HA$adB~h_4W1k>FMcr=jZ1?2_gDsRhgMd)!(KZ-9y!8+crmUKfJoS
z`gwD6^H%@?Glv5aAR+<4T3cJI1Na?)g@1Cynw_0>^f0=vi%O*u?Ck9Pd2({{b+K4{
zg?FoNG&TbW0R-CJiu{6^L;WCwv`Z-S0IXiGXGL!YuIu9P@X$XxIw~C;96UWfKK=v1
zeI$X<Oe-lM<rBA^#0*bE{SXI1fbH$=zbci=4;vdB3(aP;_wIf7J*WuW@S|6B0LC@p
zK=^Fi4yx6vv$C?%sn_e@uCA``+>>$LOE`4t{#5xIfSJQ+#vojWjRCM_SwX#CZ#5c?
zCtF)vFYnnrA`i-_4xw@(yphBaL=a6`ickQcPN(xrxm^CdSS;2bz`Oz=&Bs78Gjl&L
z=(b~}C=x1>`w0_u@(@zv4g-WdWcXHqVX4Jv_~HAKzWDvym~RE}f7ajC0B}ut?MA@>
O0000<MNUMnLSTY=sMf0h
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ccc1fa166cd89a7da7e4ba9c0d4b8a287e37cbe9
GIT binary patch
literal 364
zc%17D@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf<Z~8yL>2>u
z=YlX}jY;-GprB-lYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt(S-T^vI!
zdf!e~%scEL;Cg?_?yyi+oo}*L8qN0(N++ybz-HHwsh{{nSVC%Ai^!~sz}Z!AeLR^j
zpD0S)Z}Q=<WbZ%m(A4g-*%=O9iz@mI<wCBmVRB8&&h7ke*Y|iuwrStus`rZnLm$sk
zZrSjv<hjhMEVKU)?myt?>FGG5$ntm&bHXgONh&)z+XMtA-i*jTy=oQTs;pC+mb|vg
zy<HYL?dkQDxJeqTx;i}OJ%4WE>wnl_&)3@jS4__S7X8L;!7OMIyDdWSr;S{{erfFf
zV-lh}b|+0>5c|Q9R`+Vp=}m70Z!mv1Pvf_Ez&Ig)t^bsZ*(Pl%Ku<Gxy85}Sb4q9e
E01>8;qW}N^
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..8de4412df0a1ae35cd2ee76e4fb5934a973364d5
GIT binary patch
literal 1093
zc$@)61iJf)P)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F800004b3#c}2nYxW
zd<bNS00009a7bBm000R(000R(0q|s!N&o-=8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H11Hwr}K~z|U?UzqTBUc>9zi-}~F-AhnIx|h7w1~Eax>DLhBRz;(
zq%G{V2e*fw6c6IXNG`4ySv`0XDHKJ>vBXOe6w#&?6l53IgDr$jES6G&IwQ$8&Hv22
z9?UFLTa(SKbuazFW8QoD{eIui<To$*4dI;QRhsZh)&B<ozyM%!a+3OdzIS9<_KTue
zHz=7*zL83$?iPzh)ih0aOG`^!mgS!nMLDk*TP~NO>-uRbm5R^L&l?N?d_G^t$jC^C
z$K$CPytcO1wZFgrMLM1SkaJF~{ah{wP1B5Wx%}<;_;^H;B(qkYOeWK@w6tUZh%*3C
zRn_nDc#zNMuMNuQ^K4^d;~@ZgjSD?KKK^occD5}P3VmG*z~k{iRaHNLxBvj6D3V$M
z0MOjrT%nZatTkgSLn+N(^*cR1{d8w%r=wmzOOw@ew42-Q{#BOcqwenR$GyG1C+T#$
zX?1n=vqU0s-!#qUsv9{tIOy-|>-*(JayA4oH8u4>k|e`gx3#quMn^}#o12?^^ZfjL
z(Av-E^XiM{Z0H!;#qHMF+4;!6HVnh{p8#B&L=Y<Wb!%&D;#L6?iNwd&oDjmhy1E|K
z^1o4lt*xzh&(6-C*I!lD?*|75<F^8kNF;pGXfy<X0D#x)eKI^eywpgJhE9RqQmIr^
zI2?XZDwPxfP!#3IU@*9F-Gh4L04pmiU*&SSw*kQK_iqG)!EY#~4L28#OQ7reM*!gU
zdJmk6I|evBJbb-aEWQQ+Jv}|EPQ?L-B`BB6lv0|JBuO6_7<lSb+%Z5P5coY12;6@e
z;g<qPCX+4e>+AiJBo&5+hJL8`|9|5E;c$3Z)3i?jAd|`585<jmI0X;@Ae+rLbavC}
zbhkCvb^Sdj!j`500Gg(q#9}dnF$Qahq6m9`$QXmm<pQM?ilRKW_S@UrcPXW?$JQIM
zSj^Bg?F0ab^<okVg}h#`_bovX1kO1smCBuoi3xY9RDvW)$Y!%V91j1H%jKRKhCwba
zF8V2@x-82-0RSPFO%<^w|6+_W7=~eLn)chm!a|yJ4sgy<g&?@>_ZR?iW@hGtY&LtZ
zP$=9xIy(A%dV2bO04@iksX8P8Vl_XtZl2lbEph^Yn5IbtK|nMbEpKjaK3!Z~JhS;U
zVjP#H<^W9F9s$7F6-Q!S2_bxWdAYc^xA!CxiTvq6*lzY?%I#MMT$Z8wEm$9#$z<|$
zcXv0oy}kXnZpWcnH)d%(|LG=I-;Qk$kv-@p04#BEo9I6PZrlC>wKIJY1MhE|00000
LNkvXXu0mjfK=JgM
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..7fba36968fdd6f9ab6b94252570948e99ab8149b
GIT binary patch
literal 1692
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a3ca23a495c0294f5b15a4c7eeaf0ee751774ba7
GIT binary patch
literal 588
zc$@)D0<-;zP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004b3#c}2nYxW
zd<bNS00009a7bBm000D?000D?0em!Tp#T5?8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10m(^3K~y-6rBgAB8bK8P-t5K*OK`0MvVzTOl_KIH=Bi*Jfm9}i
zq_PRZH8wG9V<lL`#{Uqj7)Y?VLWDrH5KI#x)rGZ45fIc7^o$#KCPiG~67@K4;4w3A
z-uvdgkMEI`61Q3GR@nfkTrNjl*ENL@<T*E+jeoUTJy@^T4~ghf)3kHH-#@+bGh<Rw
zlnd2$U9(gwk*@2zN6WI#mdoX1?>?W;pXGA7vt%;))AMo8saC6*st|&7T?glU2M~=$
zKc`ZukA`7P8jVJgbAHn4bWUTj*n2NV*L4UXNZr?0Hk<w8olVpHG#Cs{7K_FGeX-Y)
zXxsK907xVf?HdC$o6Y28GC7SzBJa}a^u>(;X0zGde!u@J91edd7K?8W#T^9b^?ETy
zQ7#LGLgQGKdr1U<OeXWqFpSrlru{w&z!;O9^PLVH$AQo1L#0xAF&d4Y2LgeY@pydv
zZzAU$j4^pdiPdTq-EJ4{c3V!T(}d$VPlCbV8&y@E|9amlk))I$B3PF7G!zPn@p!x#
z4u@Y`t=6vtRlD0=NGXNK0V5*Uw(alrdi@80V<XD8<`R*_cAP1n&)>seQrR`80=NS}
a0f0Y1?YFOeCnuc%0000<MNUMnLSTYsl>ajT
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..3481bb370443322d50277fec1b38fc2a3069033d
GIT binary patch
literal 1111
zc$@)O1gQIoP)<h;3K|Lk000e1NJLTq000&M000&U1^@s6#I$TX00009a7bBm000XU
z000XU0RWnu7ytkS2}wjjR5*>*)L%?nRU8NK@42_VEv$d|bNk0qpdheS$hK_$VY(@r
zY4m|DR=41b@nvKWx<n;v=0n#Q^~FDL#PDQ%P?G7gMV~ZmfsMwnF?1_q0xKPsmeRr~
zx4r**dyfx5Lnq1falhm|oSdA`?{|L5?*I>5kO!Qz4Gj%8LWmiFSv3(8r%(leOevM;
z=jYb|6#p0Oa5yZSmGdkdT0Vw~;`3nX9xyr&%A}!@KOwOfy;u0N%9^s6PN#GK3GV3V
zaQ*FH*`0Ny`~AC~Z0vdJHDfE^jJirU5@HH>mgn%(wVw+UlOs`k((!ivbj{63B$6t7
z>iuX}S69`|_Pd93E@7}QaN2e1;0d-!bu8u5xSgJXpyXjQTk-ntZnnku82@YXw_}2}
zINs&#OhltmW!s8-dwY$M?DWnsf92ArZ+*$vSJfesm_nhbfkKB)DRins(~BS=*yC--
za()FLfA_)CGnuDfXt%ab4-5<x0D##F8w`fpFn{&*i=Ba*rkX~KM#r!!Wg!+s<O>qS
zf&{4`L(*gjih$wSFq~Egp56Cy?P%?_4^>sI-L%SJo6Tkhk&gr(?`B5l#vrIUh#R0-
zkRWO6s9caC7ggj{5$A6Ff+t${vmo+Ox7%(07aR-*4OfkkM_{QoS&Wrf$R?0iMTnXN
zQIjBR@_GVILPA)?c>E6x&5mGb<}z4@0XS3T1bf>V3<eDVVAzB`9uIe8cF^x}c(k#F
z3BzRSFWAghn2l!WREMl75DOBP@@c4A0d%9^gb*y{GH^N@iq|t!9f5{dA^@}!%yFD<
zudoY?QU+?VfV=nNU>K&f2q7SZfKm#EVL&Mb0O-07lfeX0mZ`havBq(nUe=0OET+D>
zD{w1!FJa<$RD&}ZOBf*p48wqB7#LW#ED>igqPoJ4+)A7~;qRG@#bRn1914Z>mzuk>
zph(46lkmIS%HU0WD@q7~-`$4zY7zjOc+Ph~2!%rB6$60F<;vAN+CCW!pHmM#+6k-K
zQifn-ZSJjR3l27S;_UC|)q1}5GndO%8tpRJ=ksMJPF}pA<c091smruy->Yb>_JUHn
zK1q}Ul!8)<hN?Oo-+L58w}z>b7e=n19J=81`Ld;%*a{OuD5W(0Mfd0L^_}ngA-<e!
z>NybLa%vtaA&soOhN=oXTvi7x+zxyn{!vY*=A!+_zj>eM`3xb1mMm-&l+p?Sj;b>)
zr?Nuw9dBJL_11b#PCJjKYzFb=xDsEOA$F_h>(w(e{Q%O05UH%l?cfcZ1yKF%<XP`m
z!~J{jEY9s0m9_Rtvn|r%Y#RUQ@VgUl>^zbHutEsY@4x?`kA5?O2>`dzIt_pdKp})|
d`^i6?@eh|>(KsRe-$eib002ovPDHLkV1iSY5#j&<
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a0ec6a18d115075496cfe38bb5562de5f239b42a
GIT binary patch
literal 638
zc$@)#0)hRBP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10sBcrK~y-6rIXET6Hye!e{V7-hN)s;O0^g)1k#ltq{O8~8iY{9
zO+dS_3;%$ux(T?=KL3Ery2wf>S(r^-C>qT|TnK>_QA;gO8>7xg-b{TiGWl@Qu6%HL
z@XkHwo%`;)LMg@csux5S*Z^{20JOu{E;UtWI$w+g>IETyvK~)VGdI_g=HfL{3yTm)
zdSCaj_V?(%f9nQ;Ujb^R=8!0qQX}$?xwKr&-+P2{;UWYB@CR^b3!Xg&e}LUPr1j!C
z*5S7XuwH5^rG-w)=F)O;b#n`2ZVse72>|JUbTATeR&TFin)5}V44~BuBCE#})%@K}
zgzt@$kq-DyDByxW6co}Uf8#oOELN=-L{<YfGAp-|hGB%nNpjS|*vUCTCVeFdJkWrg
zc{v>+hPTVm!RUNS*fbIVD>{%%ne#&+@FtHp+Q%h?Sa_Z~uL0!r@eSf+2;Q$Tv!pXC
z?0W#{K)bj9X+3#0Jp`US{5LsD3|;p6KLOer(6$afPt_f*H`0|5w|=_-?=)b$`|*Qo
zAAdiC80m`dV%s*|W9b4<wNg`cf*`20-n>Fd`wZfwD@r=F4qO67rPNfN(NC;dM@Nm_
zm#?w=KTdZcU2NN7_lt++(Ew_v4^vF5W)?0b&7@8$0h~yBzdS5Q((V6o1I6qx{m(f6
Y1!UK9Rti7XdH?_b07*qoM6N<$g2Sc|WdHyG
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..63911148580cae31841b3a2447d59f1e5a87ca3f
GIT binary patch
literal 431
zc$@*R0Z{&lP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10W3*GK~y-6?UFG|!%!53zdXdyp+lF1f`~XcX6#UKGq{N8P#pXN
z#XTTM$AZvRe}Yp-2Zy+c3U$jMPC6S)o0=vs5tBHiP#0f^uHNOs;ogtSIae!+f@Ml9
zwfzqOU}e!j-`1Ld12ksR0O+;c;sHQY#n`vC(@M40wRVq$QD1|v2><mN@BTKIadg&l
zi}ODLeOqf*s<k`2-4VLbAPHfw16L=clOgWSbuN=+Tjhac?Hmf74B|1wL8-<8dM2^9
zHU;3QJZKn(SuQ6bB-0sS49OIuQqHJNl#?>2<IA}g2PIH`0_qe7{@csJdfkMJ{rMGX
z+5O=NV4(7#=RG~<Y2eR2nDsOq;l0Zo=q-*>U+r2OrZDsZ(g=SN;(dJQGD98XFNpGy
ZegGIVkeRsqBMATi002ovPDHLkV1oa7v1<ST
new file mode 100644
--- /dev/null
+++ b/chat/themes/jar.mn
@@ -0,0 +1,28 @@
+chat.jar:
+% skin chat classic/1.0 %skin/classic/chat/
+	skin/classic/chat/available-16.png
+	skin/classic/chat/available.png
+	skin/classic/chat/away-16.png
+	skin/classic/chat/away.png
+	skin/classic/chat/idle-16.png
+	skin/classic/chat/idle.png
+	skin/classic/chat/mobile-16.png
+	skin/classic/chat/mobile.png
+	skin/classic/chat/offline-16.png
+	skin/classic/chat/offline.png
+	skin/classic/chat/typing-16.png
+	skin/classic/chat/typed-16.png
+	skin/classic/chat/unknown.png
+	skin/classic/chat/unknown-16.png
+	skin/classic/chat/chat-16.png
+	skin/classic/chat/chat-left-16.png
+	skin/classic/chat/conv.css
+	skin/classic/chat/browserRequest.css
+	skin/classic/chat/prpl-generic/icon32.png	(icons/prpl-generic-32.png)
+	skin/classic/chat/prpl-generic/icon48.png	(icons/prpl-generic-48.png)
+	skin/classic/chat/prpl-generic/icon.png		(icons/prpl-generic.png)
+	skin/classic/chat/prpl-unknown/icon32.png	(icons/prpl-unknown-32.png)
+	skin/classic/chat/prpl-unknown/icon48.png	(icons/prpl-unknown-48.png)
+	skin/classic/chat/prpl-unknown/icon.png		(icons/prpl-unknown.png)
+	skin/classic/chat/icons/secure.png		(icons/secure.png)
+	skin/classic/chat/icons/insecure.png		(icons/insecure.png)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..bb7156c76cc6e4d3438d680faf499a329b903f4e
GIT binary patch
literal 624
zc$@)n0+0QPP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10qsddK~y-6os+$b6G0fpf3ryl`C5ocjF5|PSLAGh=%yCI%6>?9
z7B;C>$RBZFWtBoxied3DNOLF%rm@X&D0ul&iDK5vnZ%v3$Zl>oqJ=L!3<J;n-uIcE
zMQhF9WLcKBEbI2aJVI-YUsB4rZQBcfU#rzL@C7Iku7MDu+3WS5M+1k8z#i}>=zQ?-
zQQ>N3pQVC@hMx*fj{7^ECyx(-H9$%k0qz4&8;wS+)oQJLz;0!q%JrMfHJq!p&J+MU
z0R9Abfv@xV{JZPAH-NW3V5!%gmujtrf|a_M(D2p+;B_LAIEX|d-|F>x!Z-Z#0KS$Q
zR{ROL1{?!-rqk&Mt@S<MunKnfAS)dJNGUG?=UVHcVHiIOg@V>vSA&4XR*dZk8Q{!y
zU7?gB7K@ocEHuGZjO_>mppeaGKLd%;XygFzLKCQPj9`SQZQHJt@}uK8SA-BprfFWz
zWHN2v@TvET=;%GJ>uw}YPcH!8+5v7UrC!G4@qD}89{GmZY(}@+Wjr1OP%IXyRyS9)
z`3f(9UBfU0a1w}!<2a<#Y4Z6zxm=E9GKu3jVSxAkp8<QFPR9+L9}I^>Ow*)XE>kL%
zNTpItCKJ57+eE;&?SYhX66`L7X!iU4L%e4j1PdXWK=^O`pZX0qmlpz++-A7|0000<
KMNUMnLSTY0zZDb!
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..39d032f56ad17484c06f10f29756c9c279404dd2
GIT binary patch
literal 444
zc$@*e0Ym<YP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10Xa!TK~y-6rIN8~LNOSHziR~v6pD0jm)4=Hj^fb8v6I}TkKhw^
zah5Kf`V{&C+M!De28%8A-U~M7bTC&c=vC<t2SWbj%Smz~N&KhEd-~Cqxs4Nx08!{y
zfD>Q|`~VK{Icygo-+T^S0B1l7On@(75%g2!paNV1x4;!J2VQ_T;1ievlS3TbG#ZWj
z!C){74yHjPV0g$IoC363ty-F<H2`CbkK=gU?RFz1ZBt27RnncLr+U4vZzsJkq){df
z=TfuTTuOQ<6*$<a)I++2#IMF#mX(0&ae$!2ucylg0}{Up2^AgXAPLv_1K1m72+@6g
ze#AMqR^BTDh*f+YfVGxhuLnS<)5+JkB7kQV-<dJSTWd@0b{pp$)>@1)9+>R{D4hHO
mjN>>S_xt_p@OA4T0pni+{jVuDxBmkG0000<MNUMnLSTZ$s<Ea3
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5d862c470280ea2b1a52b65cc1d12af8e641465d
GIT binary patch
literal 570
zc$@(`0>%A_P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10k=s+K~y-6rISCa8bK7rf4XLISuF%nTyV?R%cc)@L9B)NCG1k{
z2bkV3@lw1tvDr>7V`m{j5G-7gjV}IK!5KA;ioPfYGH|%u!<>6&?#zuHk4LVT>$)#M
z9dJ$n=$-H?g!py6KXxwQx^4_;=(;W($1w_p0=Zlcr4+vJGnq{2_xoEZr3*BL5XVb_
zllZ%B+m&jyN-C9Nzu%*jx<sWEVHnbGx0%gmEubodIL1#WY1p=1sn_e6rioGtDdi26
zQY4c}%H=ZIY_<Y40JPn1_oD0iTdh|6kVqshZtvZ%xlAU*cs%~(dEQ$bsFzA5!!*r{
z(>?d=sOvh#V$lG;BmgI$&m*P0alF@qYc8Em1AI&XPBxppDez3Jl)^9!04D(;r9{)T
zr=s^9Kpf~zr_+b`e$<OF3;}v^py&JkKR57<<#Gw|GY-5C27~Qlv3M#vbDPbEAPBa=
zH?7m@{PsL=vsf%XTbA|k?XUCEXvA)}`znO^asG+TAP8FBZkOe9`OrOzB8I~uQ53a+
z=G|e^G)-8RWtgT(GMQw*-?Lh+Sg+U2=kx91aQJ(e{%4&30Qz+TW#zs-761SM07*qo
IM6N<$f|*?auK)l5
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..f5598da11205202a38ce3a9beb7148fa3bb33289
GIT binary patch
literal 399
zc%17D@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf<Z~8yL>2?p
zUk71ECym(^Ktah8*NBqf{Irtt#G+J&^73-M%)IR4<ivthz5Jr|+3#$m7#J9NJY5_^
zEPCHg+St|XAkb$2Rl#-37LVD-%sm7-JnkK77wFq-RWw1|tG>WVfq!Fbf+CA+Nx5p(
zS;fYJ*-CGDd3X;0`+w%of2+$TAH8<yFZ|ERP*MHOfny_UP{QZiR?NqBPVZxsnceHu
z@m+y~@Auz(v1`L5k|zXBdj9i9>0S3}sq^cFI25-z3e>22KB}76ZkTp%S*FEYzvTPh
zt!J}k${b(!*g|9Jvp2uzWzOnYbb>W#Hrs3io<Og&leb<ge*4?*z%i{w*Jj;*`;FmR
z)Z!^jZ_MT`o$~qCcXf@WD^_tm$WgY4)0@6=-D~!O+uKB34VjKFH`5dAK3ui8Z2D={
rV-oAGYo|u`9X7a;yGMCr-4gka@&_e$iiG6=1CPPe)z4*}Q$iB}a)zP&
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..7097210a5fe95c8cfc56b0c06b33493aa292a2f3
GIT binary patch
literal 412
zc%17D@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf<Z~8yL>2?p
zUk71ECym(^Ktah8*NBqf{Irtt#G+J&^73-M%)IR4<ivthz5Jr|+3#$m7#J8OJzX3_
zEPBsQ-0yWbLF8!tcjJ1O-QMbtxqo=QE7O?5S9+4Y@B`y4n}!Eh7XENx<ZP9iyX(db
zw|%b{ybZ|UDEoLQQg6=T_xF@noia@BD`J*&kU4ICY)1R8b-!bM5+(LoJ%2YzrP3vI
z<2Aj{HsQ}>)Sp(ayLM~os(%7kO7HeB;sA=g(dfEysp@uW_~(CbnEu^5X0K8q$rHsJ
z9(hf!z;f<3wm0*$7@8C`x)(WwTCddL0x}tG1Pvd5ym2mV^ODS2-46>+)Y!%Ai*;{y
z7TNcHe`si`>yZ=sM>PxVX8PQ#-g~cVYm{wjWLbRZ)rDD7514;^3Ax&Ng3ZC@T59CI
zWtqS3d}3qW*U-CaS8grSjEfm1ymLw>FuH{PWP7{#?2>t1f~SEY$>8bg=d#Wzp$P!T
C#jM`|
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..7d9c14c79040ee53cc1d70645c815893ce8bf555
GIT binary patch
literal 5593
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e16094acbf2f56ae39182c73577d1eb16c7d94f8
GIT binary patch
literal 716
zc$@*u0yF)IP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10!c|kK~y-6rBh8y<4_bmH;JaT3bu-ph+$l~kqTnV;M#0NTzAob
zqAOSFKbc?PuDB5wgCGc&Rv1LoA+f=a;wO`LU+!!s!P1%4Jh<HV&O7&m^WG&9!RNKN
zw>LvXt0Lk6aR0y|5k(?$9t6R|XFg>KhzPs8yBoG``|IoLmg6{>o0|hOBZ?w~VTewr
z!%C^cR;%?rj}wtBxaoP`^3Ki<CMPGMwFWcio|!Qi3~+RGggB0Wv|6oiA_8-Ne;-Mb
zY<Qmcwb^XKvMgw=5yvsQ-7c6JwrxXe4Z|?tc^>-x{!Fb_`{n!oPcuo98QZoSjYh+;
zEDOwxgM$N{o}OZ2VgkKh4`*j*SXx>_nx@cNqg*cI=H}*0yWQ>@M6|lLwq}(|B`Bp3
zMG-D9FR{J7jasdSt*tE-3I+6fJuowrQWzf}$Kv9mB_dTzM4b8gc_^izwFUreZf>Gj
zEaq6HQo+;H6O>Z9E>lxeAR^ZS;Lgs@=HybTghHW^pZonjuCA_7uh(;onNchj0l>8Y
zKq&>&G)GjA;~2-s$5>fe!SwX>P=c&m13-9pcL$}^kTG*Ep5r(<mifV8005yuL{St)
zLxjwppP#X?u<*}25eW?uIls8L;7p{?WZ(BeL?fNOy}jZ7{+@}bV+29)z|4myCnrdf
zWT;ZR-TrI!>+5TzX?hq0!Nbs3E|<&8)oK-EV`D>NX8Vti58U3~;{E+S|H>o7WSXY$
yx~`Qyr`8%TFE4m~eZ}MBBd2LPI!ymF&c6W_g6qn2x<^d_0000<MNUMnLSTZ(Jwz@5
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..8851de704855747d4f190fa13c493137a257bdfd
GIT binary patch
literal 456
zc%17D@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf<Z~8yL>2?p
zUk71ECym(^Ktah8*NBqf{Irtt#G+J&^73-M%)IR4<ivthz5Jr|+3#$m7#J8`JzX3_
zEPCHgvduf>AkwlwH9Rju+{7e|i)#w=g@p><g0krg9*BwPY3^{{!5YgYa&e}!fOF%7
z2|o?}JH9UKn8c@XDeaZ{;)^x@mG6IcYt<M1XJ<%|lU4Ej^kh!9_EIH=_kX>zZRLC2
z<}<vKJNB^PN6_l4an^JDw7QrYHtBd?zVf{CpPgL4eI6T7i%8qU1Otf`CsPhREMQRJ
zao%-T@AAtl^UpuuK3m%2`K>~k)jFq7e4Z1x-d*PSn=)&&*}P}df-RrlGB*$Ka{XE*
zo6PgKPF^t4Y_=c^<1vXfTQ!z0+O6EhaZF;}b?xq>O_yJ~tiHNP$J>QvNAtz%{ZV?;
zJ2;dMeXLOFQfg9gaJiAUz2beixpc<b=b5wYqO(OCwtl-FBf=Uvt#a+xKZ?h)w*I=6
wVREHz|MH-pdVAv5|9x7tv;U)ezuZf1_cN>a=v}<!3k-DzPgg&ebxsLQ0QJbaU;qFB
--- a/mail/app/profile/all-thunderbird.js
+++ b/mail/app/profile/all-thunderbird.js
@@ -769,11 +769,15 @@ pref("browser.search.order.3", "chrome:/
 pref("browser.search.update", false);
 
 // Check whether we need to perform engine updates every 6 hours
 pref("browser.search.update.interval", 21600);
 
 // Disable remote debugging protocol logging
 pref("devtools.debugger.log", false);
 
+pref("mail.chat.enabled", true);
+// Send typing notification in private conversations
+pref("purple.conversations.im.send_typing", true);
+
 // BigFiles
 pref("mail.cloud_files.enabled", true);
 pref("mail.cloud_files.inserted_urls.footer.link", "http://www.getthunderbird.com");
--- a/mail/base/content/folderPane.js
+++ b/mail/base/content/folderPane.js
@@ -1236,16 +1236,19 @@ let gFolderTreeView = {
 
       // Don't show deferred pop accounts
       accounts = accounts.filter(function isNotDeferred(a) {
         let server = a.incomingServer;
         return !(server instanceof Ci.nsIPop3IncomingServer &&
                  server.deferredToAccount);
       });
 
+      // Don't show IM accounts
+      accounts = accounts.filter(function(a) a.incomingServer.type != "im");
+
       function sortAccounts(a, b) {
         if (a.key == acctMgr.defaultAccount.key)
           return -1;
         if (b.key == acctMgr.defaultAccount.key)
           return 1;
         let aIsNews = a.incomingServer.type == "nntp";
         let bIsNews = b.incomingServer.type == "nntp";
         if (aIsNews && !bIsNews)
@@ -1713,16 +1716,21 @@ let gFolderTreeView = {
 
     let acctMgr = Cc["@mozilla.org/messenger/account-manager;1"]
                      .getService(Ci.nsIMsgAccountManager);
     for each (let acct in fixIterator(acctMgr.accounts, Ci.nsIMsgAccount)) {
       // Skip deferred accounts
       if (acct.incomingServer instanceof Ci.nsIPop3IncomingServer &&
           acct.incomingServer.deferredToAccount)
         continue;
+
+      // Skip IM accounts
+      if (acct.incomingServer.type == "im")
+        continue;
+
       folders.push(acct.incomingServer.rootFolder);
       this.addSubFolders(acct.incomingServer.rootFolder, folders);
     }
     return folders;
   },
 
   /**
    * This is a recursive function to add all subfolders to the array. It
--- a/mail/base/content/glodaFacetBindings.xml
+++ b/mail/base/content/glodaFacetBindings.xml
@@ -1542,24 +1542,37 @@
         let message = this.message;
         let dis = this;
         function anonElem(aAnonId) {
           return document.getAnonymousElementByAttribute(dis, "anonid",
                                                          aAnonId);
         }
         let subject = anonElem("subject");
         // -- eventify
-        subject.onclick = function(aEvent) {
-          FacetContext.showConversationInTab(message,
-                                             aEvent.button == 1);
+        if (FacetContext.searcher instanceof GlodaIMSearcher) {
+          subject.onclick = function(aEvent) {
+            FacetContext.showIMConversationInTab(message,
+                                                 aEvent.button == 1);
+          }
+          subject.onkeypress = function(aEvent) {
+            if (aEvent.keyCode == aEvent.DOM_VK_RETURN)
+              FacetContext.showIMConversationInTab(message,
+                                                   aEvent.shiftKey == true);
+          }
         }
-        subject.onkeypress = function(aEvent) {
-          if (aEvent.keyCode == aEvent.DOM_VK_RETURN)
+        else {
+          subject.onclick = function(aEvent) {
             FacetContext.showConversationInTab(message,
-                                               aEvent.shiftKey == true);
+                                               aEvent.button == 1);
+          }
+          subject.onkeypress = function(aEvent) {
+            if (aEvent.keyCode == aEvent.DOM_VK_RETURN)
+              FacetContext.showConversationInTab(message,
+                                                 aEvent.shiftKey == true);
+          }
         }
 
         // -- Content Poking
         if (message.subject.trim() == "")
           subject.textContent = glodaFacetStrings.get("glodaFacetView.result.message.noSubject");
         else
           subject.textContent = message.subject;
         let authorNode = anonElem("author");
--- a/mail/base/content/glodaFacetView.css
+++ b/mail/base/content/glodaFacetView.css
@@ -201,17 +201,17 @@ h1, h2, h3 {
 }
 
 #filter-header-label {
   margin: 0;
   margin-top: 1em;
 }
 
 .facetious[uninitialized] {
-  display: none;
+  display: none !important;
 }
 
 .facetious {
   display: list-item; /* take the whole column width */
   list-style: none;
   padding: 2px;
 }
 
--- a/mail/base/content/glodaFacetView.js
+++ b/mail/base/content/glodaFacetView.js
@@ -324,18 +324,25 @@ ActiveNonSingularConstraint.prototype = 
   },
   isExcludedGroup: function(aGroupValue) {
     let valId = aGroupValue[this.facetDef.groupIdAttr];
     return (valId in this.excludedGroupIds);
   }
 };
 
 var FacetContext = {
-  facetDriver: new FacetDriver(Gloda.lookupNounDef("message"),
-                               window),
+  get facetDriver() {
+    if (!("GlodaIMSearcher" in window))
+      Cu.import("resource:///modules/search_im.js");
+    let nounName =
+      this.searcher instanceof GlodaIMSearcher ? "im-conversation" : "message";
+    delete this.facetDriver;
+    this.facetDriver = new FacetDriver(Gloda.lookupNounDef(nounName), window);
+    return this.facetDriver;
+  },
 
   /**
    * The root collection which our active set is a subset of.  We hold onto this
    *  for garbage collection reasons, although the tab that owns us should also
    *  be holding on.
    */
   _collection: null,
   set collection(aCollection) {
@@ -840,16 +847,31 @@ var FacetContext = {
       conversation: aMessage.conversation,
       message: aMessage,
       title: aMessage.conversation.subject,
       background: aBackground
     });
   },
 
   /**
+   * Show the conversation in a new glodaList tab.
+   *
+   * @param {GlodaIMConversation} aConversation The conversation to show.
+   * @param {Boolean} [aBackground] Whether it should be in the background.
+   */
+  showIMConversationInTab: function(aConversation, aBackground) {
+    let tabmail = this.rootWin.document.getElementById("tabmail");
+    tabmail.openTab("chat", {
+      convType: "log",
+      conv: aConversation,
+      background: aBackground
+    });
+  },
+
+  /**
    * Show the message in a new tab.
    *
    * @param {GlodaMessage} aMessage The message to show.
    * @param {Boolean} [aBackground] Whether it should be in the background.
    */
   showMessageInTab: function(aMessage, aBackground) {
     let tabmail = this.rootWin.document.getElementById("tabmail");
     let msgHdr = aMessage.folderMessage;
--- a/mail/base/content/hiddenWindow.xul
+++ b/mail/base/content/hiddenWindow.xul
@@ -99,12 +99,14 @@
 </commandset>
 
   <!-- it's the whole mailWindowOverlay.xul menubar! hidden windows need to
        have a menubar for situations where they're the only window remaining
        on a platform that wants to leave the app running, like the Mac.
   -->
 
 <toolbox id="navigation-toolbox" class="toolbox-top"/>
-  
+
+<browser id="hiddenBrowser" disablehistory="true"/>
+
 </window>
 
 #endif
--- a/mail/base/content/mailCore.js
+++ b/mail/base/content/mailCore.js
@@ -371,16 +371,25 @@ function focusOnMail(tabNo, event)
   }
 }
 
 function toAddressBook() 
 {
   toOpenWindowByType("mail:addressbook", "chrome://messenger/content/addressbook/addressbook.xul");
 }
 
+function showChatTab()
+{
+  let tabmail = document.getElementById("tabmail");
+  if (gChatTab)
+    tabmail.switchToTab(gChatTab);
+  else
+    tabmail.openTab("chat", {});
+}
+
 function toImport()
 {
   window.openDialog("chrome://messenger/content/importDialog.xul","importDialog","chrome, modal, titlebar, centerscreen");
 }
 
 // aPaneID
 function openOptionsDialog(aPaneID, aTabID)
 {
@@ -453,16 +462,49 @@ function openAddonsMgr(aView)
 }
 
 function openActivityMgr()
 {
   Components.classes['@mozilla.org/activity-manager-ui;1'].
     getService(Components.interfaces.nsIActivityManagerUI).show(window);
 }
 
+function openIMAccountMgr()
+{
+  var win = Services.wm.getMostRecentWindow("Messenger:Accounts");
+  if (win)
+    win.focus();
+  else {
+    win = Services.ww.openWindow(null,
+                                 "chrome://messenger/content/chat/imAccounts.xul",
+                                 "Accounts", "chrome,resizable,centerscreen",
+                                 null);
+  }
+  return win;
+}
+
+function openIMAccountWizard()
+{
+  const kFeatures = "chrome,centerscreen,modal,titlebar";
+  const kUrl = "chrome://messenger/content/chat/imAccountWizard.xul";
+  const kName = "IMAccountWizard";
+
+#ifdef XP_MACOSX
+  // On Mac, avoid using the hidden window as a parent as that would
+  // make it visible.
+  let hiddenWindowUrl =
+    Services.prefs.getCharPref("browser.hiddenWindowChromeURL");
+  if (window.location.href == hiddenWindowUrl) {
+    Services.ww.openWindow(null, kUrl, kName, kFeatures, null);
+    return;
+  }
+#endif
+  window.openDialog(kUrl, kName, kFeatures);
+}
+
 function openSavedFilesWnd()
 {
   Components.classes['@mozilla.org/download-manager-ui;1']
             .getService(Components.interfaces.nsIDownloadManagerUI)
             .show(window);
 }
 
 function SetBusyCursor(window, enable)
--- a/mail/base/content/mailWindowOverlay.xul
+++ b/mail/base/content/mailWindowOverlay.xul
@@ -412,16 +412,17 @@
   <key id="key_collapseAllThreads" key="&collapseAllThreadsCmd.key;" oncommand="goDoCommand('cmd_collapseAllThreads')"/>
   <key key="&collapseAllThreadsCmd.key;" modifiers="shift"           oncommand="goDoCommand('cmd_collapseAllThreads')"/>
   <key id="key_nextUnreadThread" key="&nextUnreadThread.key;"        oncommand="goDoCommand('cmd_nextUnreadThread')"/>
   <key id="key_previousMsg" key="&prevMsgCmd.key;"                   oncommand="goDoCommand('cmd_previousMsg')"/>
   <key id="key_previousUnreadMsg" key="&prevUnreadMsgCmd.key;"       oncommand="goDoCommand('cmd_previousUnreadMsg')"/>
   <key id="key_archive" key="&archiveMsgCmd.key;"                    oncommand="goDoCommand('cmd_archive')"/>
   <key id="key_goForward" key="&goForwardCmd.commandKey;"            oncommand="goDoCommand('cmd_goForward')"/>
   <key id="key_goBack" key="&goBackCmd.commandKey;"                  oncommand="goDoCommand('cmd_goBack')"/>
+  <key id="key_goChat" key="&goChatCmd.commandKey;"                  modifiers="accel,shift"/>
   <key id="key_goStartPage" keycode="VK_HOME"                        oncommand="goDoCommand('cmd_goStartPage')" modifiers="alt"/>
   <key id="key_undoCloseTab" key="&undoCloseTabCmd.commandkey;"      oncommand="goDoCommand('cmd_undoCloseTab')" modifiers="accel, shift"/> 
   <key id="key_reply" key="&replyMsgCmd.key;"                        oncommand="goDoCommand('cmd_reply')" modifiers="accel"/>
   <key id="key_replyall" key="&replyToAllMsgCmd.key;"                oncommand="goDoCommand('cmd_replyall')" modifiers="accel, shift"/>
   <key id="key_replylist" key="&replyToListMsgCmd.key;"              oncommand="goDoCommand('cmd_replylist')" modifiers="accel, shift"/>
   <key id="key_forward" key="&forwardMsgCmd.key;"                    oncommand="goDoCommand('cmd_forward')" modifiers="accel"/>
   <key id="key_editAsNew" key="&editMsgAsNewCmd.key;"                oncommand="goDoCommand('cmd_editAsNew')" modifiers="accel"/>
   <key id="key_watchThread" key="&watchThreadMenu.key;"              oncommand="goDoCommand('cmd_watchThread')" />
@@ -998,16 +999,20 @@
               <menuitem id="newCreateEmailAccountMenuItem"
                         label="&newCreateEmailAccountCmd.label;"
                         accesskey="&newCreateEmailAccountCmd.accesskey;"
                         oncommand="NewMailAccountProvisioner(msgWindow);"/>
               <menuitem id="newMailAccountMenuItem"
                         label="&newExistingEmailAccountCmd.label;"
                         accesskey="&newExistingEmailAccountCmd.accesskey;"
                         oncommand="NewMailAccount(msgWindow);"/>
+              <menuitem id="newIMAccountMenuItem"
+                        label="&newIMAccountCmd.label;"
+                        accesskey="&newIMAccountCmd.accesskey;"
+                        oncommand="openIMAccountWizard();"/>
               <menuitem id="newAccountMenuItem"
                         label="&newOtherAccountsCmd.label;"
                         accesskey="&newOtherAccountsCmd.accesskey;"
                         oncommand="MsgAccountWizard();"/>
               <menuseparator id="newPopupMenuSeparator"/>
               <menuitem id="menu_newCard"/>
             </menupopup>
           </menu>
@@ -1414,16 +1419,20 @@
           </menu>
           <menuitem id="menu_goForward" label="&goForwardCmd.label;"
                     accesskey="&goForwardCmd.accesskey;" command="cmd_goForward"
                     key="key_goForward"/>
           <menuitem id="menu_goBack" label="&goBackCmd.label;"
                     accesskey="&goBackCmd.accesskey;" command="cmd_goBack"
                     key="key_goBack"/>
           <menuseparator id="goNextSeparator"/>
+          <menuitem id="menu_goChat" label="&goChatCmd.label;"
+                    accesskey="&goChatCmd.accesskey;" oncommand="showChatTab();"
+                    key="key_goChat"/>
+          <menuseparator id="goChatSeparator"/>
           <menu id="goFolderMenu"
                 label="&folderMenu.label;"
                 accesskey="&folderMenu.accesskey;"
                 oncommand="gFolderTreeView.selectFolder(event.target._folder, true)">
             <menupopup id="menu_GoFolderPopup"
                        type="folder"
                        showFileHereLabel="true"
                        fileHereLabel="&thisFolder.label;"
@@ -1644,16 +1653,29 @@
                   accesskey="&savedFiles.accesskey;"
                   key="key_savedFiles"
                   oncommand="openSavedFilesWnd();"/>
         <menuitem id="addonsManager" label="&addons.label;" accesskey="&addons.accesskey;"
                   oncommand="openAddonsMgr();"/>
         <menuitem id="activityManager" label="&activitymanager.label;"
                   accesskey="&activitymanager.accesskey;"
                   oncommand="openActivityMgr();"/>
+        <menu id="imAccountsStatus" label="&imAccountsStatus.label;"
+              accesskey="&imAccountsStatus.accesskey;"
+              oncommand="chatHandler.setStatusMenupopupCommand(event);">
+          <menupopup id="imStatusMenupopup">
+            <menuitem id="imStatusAvailable" status="available" label="&imStatus.available;" class="menuitem-iconic"/>
+            <menuitem id="imStatusUnavailable" status="unavailable" label="&imStatus.unavailable;" class="menuitem-iconic"/>
+            <menuseparator id="imStatusOfflineSeparator"/>
+            <menuitem id="imStatusOffline" status="offline" label="&imStatus.offline;" class="menuitem-iconic"/>
+            <menuseparator id="imStatusShowAccountsSeparator"/>
+            <menuitem id="imStatusShowAccounts" label="&imStatus.showAccounts;"/>
+          </menupopup>
+        </menu>
+
         <menuseparator id="devToolsSeparator"/>
         <menuitem id="filtersCmd" label="&filtersCmd.label;"
                   accesskey="&filtersCmd.accesskey;"
                   command="cmd_displayMsgFilters"/>
         <menuitem id="applyFilters"
                   label="&filtersApply.label;"
                   accesskey="&filtersApply.accesskey;"
                   command="cmd_applyFilters"/>
@@ -1965,16 +1987,21 @@
         <menuseparator id="button-afterTagRemoveAllSeparator"/>
       </menupopup>
     </toolbarbutton>
     <toolbarbutton id="button-address"
                    class="toolbarbutton-1"
                    label="&addressBookButton.label;"
                    oncommand="toAddressBook();"
                    tooltiptext="&addressBookButton.tooltip;"/>
+    <toolbarbutton id="button-chat"
+                   class="toolbarbutton-1"
+                   label="&chatButton.label;"
+                   oncommand="showChatTab();"
+                   tooltiptext="&chatButton.tooltip;"/>
     <toolbaritem id="throbber-box" title="&throbberItem.title;" align="center" pack="center"
                  mousethrough="always">
        <image/>
     </toolbaritem>
     <toolbarbutton id="button-stop"
                    class="toolbarbutton-1"
                    label="&stopButton.label;"
                    tooltiptext="&stopButton.tooltip;"
@@ -1990,19 +2017,19 @@
            class="inline-toolbar chromeclass-toolbar"
            toolbarname="&showMessengerToolbarCmd.label;"
            accesskey="&showMessengerToolbarCmd.accesskey;"
            fullscreentoolbar="true" mode="full"
            customizable="true"
            context="toolbar-context-menu"
 #ifdef XP_MACOSX
            iconsize="small"
-           defaultset="button-getmsg,button-newmsg,button-address,spacer,button-tag,qfb-show-filter-bar,spring,gloda-search">
+           defaultset="button-getmsg,button-newmsg,button-chat,button-address,spacer,button-tag,qfb-show-filter-bar,spring,gloda-search">
 #else
-           defaultset="button-getmsg,button-newmsg,button-address,separator,button-tag,qfb-show-filter-bar,spring,gloda-search">
+           defaultset="button-getmsg,button-newmsg,button-chat,button-address,separator,button-tag,qfb-show-filter-bar,spring,gloda-search">
 #endif
   </toolbar>
 
   <toolbarset id="customToolbars" context="toolbar-context-menu"/>
 </toolbox>
 
 <!-- The msgNotificationBar appears on top of the message and displays
      information like: junk, contains remote images, or is a suspected phishing
--- a/mail/base/modules/mailMigrator.js
+++ b/mail/base/modules/mailMigrator.js
@@ -128,17 +128,17 @@ var MailMigrator = {
 
   /**
    * Determine if the UI has been upgraded in a way that requires us to reset
    * some user configuration.  If so, performs the resets.
    */
   _migrateUI: function MailMigrator__migrateUI() {
     // The code for this was ported from
     // mozilla/browser/components/nsBrowserGlue.js
-    const UI_VERSION = 3;
+    const UI_VERSION = 4;
     const MESSENGER_DOCURL = "chrome://messenger/content/messenger.xul#";
     const UI_VERSION_PREF = "mail.ui-rdf.version";
     let currentUIVersion = 0;
 
     try {
       currentUIVersion = Services.prefs.getIntPref(UI_VERSION_PREF);
     } catch(ex) {}
 
@@ -228,16 +228,44 @@ var MailMigrator = {
               // If there's no gloda-search, just put the QFB toggle at the end
               currentSet = currentSet + ",qfb-show-filter-bar";
             }
             this._setPersist(barResource, currentSetResource, currentSet);
           }
         }
       }
 
+      // In UI version 4, we add the chat button to the mail toolbar.
+      if (currentUIVersion < 4) {
+        let currentSetResource = this._rdf.GetResource("currentset");
+        let barResource = this._rdf.GetResource(MESSENGER_DOCURL + "mail-bar3");
+        if (barResource !== null) {
+          let currentSet = this._getPersist(barResource, currentSetResource);
+
+          if (currentSet
+              && currentSet.indexOf("button-chat") == -1) {
+
+            dirty = true;
+            if (currentSet.indexOf("button-newmsg") != -1) {
+              // Put the chat button after the newmsg button.
+              currentSet = currentSet.replace(/(^|,)button-newmsg($|,)/,
+                                              "$1button-newmsg,button-chat$2");
+            } else if (currentSet.indexOf("button-address") != -1) {
+              // If there's no newmsg button, put the chat button before the address book button.
+              currentSet = currentSet.replace(/(^|,)button-address($|,)/,
+                                              "$1button-chat,button-address$2");
+            } else {
+              // Otherwise, just put the chat button at the end.
+              currentSet = currentSet + ",button-chat";
+            }
+            this._setPersist(barResource, currentSetResource, currentSet);
+          }
+        }
+      }
+
       // Update the migration version.
       Services.prefs.setIntPref(UI_VERSION_PREF, UI_VERSION);
 
     } catch(e) {
       Cu.reportError("Migrating from UI version " + currentUIVersion + " to "
                      + UI_VERSION + " failed. Error message was: " + e + " -- "
                      + "Will reattempt on next start.");
     } finally {
--- a/mail/build.mk
+++ b/mail/build.mk
@@ -66,18 +66,20 @@ endif
 
 tier_app_dirs += $(MOZ_BRANDING_DIRECTORY)
 
 ifdef MOZ_CALENDAR
 tier_app_dirs += calendar/lightning
 endif
 
 tier_app_dirs += \
+	chat \
 	mail \
 	$(NULL)
+#	purple instantbird
 
 installer:
 	@$(MAKE) -C mail/installer installer
 
 package:
 	@$(MAKE) -C mail/installer
 
 package-compare:
--- a/mail/components/Makefile.in
+++ b/mail/components/Makefile.in
@@ -39,17 +39,17 @@ DEPTH   = ../..
 topsrcdir = @top_srcdir@
 srcdir    = @srcdir@
 VPATH   = @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 # Only Mac and Windows have search integration components, but we include at
 # least one module from search/ on all platforms
-DIRS    = compose cloudfile preferences addrbook migration activity search about-support wintaskbar newmailaccount
+DIRS    = compose cloudfile preferences addrbook migration activity search about-support wintaskbar newmailaccount im
 
 ifneq (,$(filter windows gtk2 cocoa, $(MOZ_WIDGET_TOOLKIT)))
 DIRS += shell
 endif
 
 ifdef MOZ_SAFE_BROWSING
 DIRS += phishing 
 endif
new file mode 100644
--- /dev/null
+++ b/mail/components/im/Makefile.in
@@ -0,0 +1,57 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is Thunderbird.
+#
+# The Initial Developer of the Original Code is
+#  Florian Quèze <florian@mozilla.com>.
+# Portions created by the Initial Developer are Copyright (C) 2011
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH     = ../../..
+topsrcdir = @top_srcdir@
+srcdir    = @srcdir@
+VPATH     = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+EXTRA_COMPONENTS = \
+		imIncomingServer.js \
+		imProtocolInfo.js \
+		im.manifest \
+		$(NULL)
+
+EXTRA_JS_MODULES = \
+		modules/index_im.js \
+		modules/search_im.js \
+		$(NULL)
+
+PREF_JS_EXPORTS = $(srcdir)/all-im.js
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/mail/components/im/all-im.js
@@ -0,0 +1,4 @@
+pref("messenger.options.messagesStyle.theme", "messenger-messagestyles");
+pref("messenger.options.emoticonsTheme", "messenger-emoticons");
+pref("messenger.conversations.textbox.autoResize", true);
+pref("messenger.conversations.doubleClickToReply", true);
new file mode 100644
--- /dev/null
+++ b/mail/components/im/content/addbuddy.js
@@ -0,0 +1,71 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2007.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+Components.utils.import("resource:///modules/iteratorUtils.jsm");
+Components.utils.import("resource:///modules/imServices.jsm");
+
+var addBuddy = {
+  onload: function ab_onload() {
+    let accountList = document.getElementById("accountlist");
+    for (let acc in fixIterator(Services.accounts.getAccounts())) {
+      if (!acc.connected)
+        continue;
+      let proto = acc.protocol;
+      let item = accountList.appendItem(acc.name, acc.id, proto.name);
+      item.setAttribute("image", proto.iconBaseURI + "icon.png");
+      item.setAttribute("class", "menuitem-iconic");
+    }
+    if (!accountList.itemCount) {
+      document.getElementById("addBuddyDialog").cancelDialog();
+      throw "No connected account!";
+    }
+    accountList.selectedIndex = 0;
+  },
+
+  oninput: function ab_oninput() {
+    document.documentElement.getButton("accept").disabled =
+      !addBuddy.getValue("name");
+  },
+
+  getValue: function ab_getValue(aId) document.getElementById(aId).value,
+
+  create: function ab_create() {
+    let account = Services.accounts.getAccountById(this.getValue("accountlist"));
+    let group = document.getElementById("chatBundle").getString("defaultGroup");
+    account.addBuddy(Services.tags.createTag(group), this.getValue("name"));
+  }
+};
new file mode 100644
--- /dev/null
+++ b/mail/components/im/content/addbuddy.xul
@@ -0,0 +1,75 @@
+<?xml version="1.0"?>
+<!-- ***** BEGIN LICENSE BLOCK *****
+   - Version: MPL 1.1/GPL 2.0/LGPL 2.1
+   -
+   - The contents of this file are subject to the Mozilla Public License Version
+   - 1.1 (the "License"); you may not use this file except in compliance with
+   - the License. You may obtain a copy of the License at
+   - http://www.mozilla.org/MPL/
+   -
+   - Software distributed under the License is distributed on an "AS IS" basis,
+   - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+   - for the specific language governing rights and limitations under the
+   - License.
+   -
+   - The Original Code is the Instantbird messenging client, released
+   - 2007.
+   -
+   - The Initial Developer of the Original Code is
+   - Florian QUEZE <florian@instantbird.org>.
+   - Portions created by the Initial Developer are Copyright (C) 2007
+   - the Initial Developer. All Rights Reserved.
+   -
+   - Contributor(s):
+   -
+   - Alternatively, the contents of this file may be used under the terms of
+   - either the GNU General Public License Version 2 or later (the "GPL"), or
+   - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+   - in which case the provisions of the GPL or the LGPL are applicable instead
+   - of those above. If you wish to allow use of your version of this file only
+   - under the terms of either the GPL or the LGPL, and not to allow others to
+   - use your version of this file under the terms of the MPL, indicate your
+   - decision by deleting the provisions above and replace them with the notice
+   - and other provisions required by the GPL or the LGPL. If you do not delete
+   - the provisions above, a recipient may use your version of this file under
+   - the terms of any one of the MPL, the GPL or the LGPL.
+   -
+   - ***** END LICENSE BLOCK ***** -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/imMenulist.css" type="text/css"?>
+
+<!DOCTYPE window SYSTEM "chrome://messenger/locale/addbuddy.dtd">
+
+<dialog
+  id     = "addBuddyDialog"
+  windowtype="Messenger:AddBuddy"
+  title  = "&addBuddyWindow.title;"
+  buttons= "accept,cancel"
+  xmlns  = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+  onload = "addBuddy.onload()"
+  ondialogaccept="addBuddy.create()"
+  buttondisabledaccept="true">
+  <script type="application/javascript" src="chrome://messenger/content/chat/addbuddy.js"/>
+
+  <stringbundle id="chatBundle" src="chrome://messenger/locale/chat.properties"/>
+
+  <grid>
+   <columns>
+    <column/>
+    <column flex="1"/>
+   </columns>
+   <rows>
+
+    <row id="nameBox" align="baseline">
+     <label value="&name.label;" control="name"/>
+     <textbox id="name" oninput="addBuddy.oninput()"/>
+    </row>
+
+    <row id="accountBox" align="center">
+     <label value="&account.label;" control="accountlist"/>
+     <menulist id="accountlist"/>
+    </row>
+   </rows>
+  </grid>
+</dialog>
new file mode 100644
--- /dev/null
+++ b/mail/components/im/content/am-im.js
@@ -0,0 +1,182 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2007.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+Cu.import("resource://gre/modules/Services.jsm");
+
+const autoJoinPref = "autoJoin";
+
+function onPreInit(aAccount, aAccountValue)
+{
+  account.init(aAccount.incomingServer.wrappedJSObject.imAccount);
+}
+
+var account = {
+  init: function account_init(aAccount) {
+    this.account = aAccount;
+    this.proto = this.account.protocol;
+    document.getElementById("accountName").value = this.account.name;
+    document.getElementById("protocolName").value = this.proto.name || this.proto.id;
+    document.getElementById("protocolIcon").src =
+      this.proto.iconBaseURI + "icon48.png";
+
+    let password = document.getElementById("server.password");
+    let passwordBox = document.getElementById("passwordBox");
+    if (this.proto.noPassword) {
+      passwordBox.hidden = true;
+      password.removeAttribute("wsm_persist");
+    }
+    else {
+      passwordBox.hidden = false;
+      // Should we force layout here to ensure password.value works?
+      password.value = this.account.password;
+      password.setAttribute("wsm_persist", "true");
+    }
+
+    document.getElementById("server.alias").value = this.account.alias;
+
+    let protoId = this.proto.id;
+    let canAutoJoin =
+      protoId == "prpl-irc" || protoId == "prpl-jabber" || protoId == "prpl-gtalk";
+    document.getElementById("optionalSeparator").hidden = !canAutoJoin;
+    document.getElementById("autojoinBox").hidden = !canAutoJoin;
+    let autojoin = document.getElementById("server.autojoin");
+    if (canAutoJoin)
+      autojoin.setAttribute("wsm_persist", "true");
+    else
+      autojoin.removeAttribute("wsm_persist");
+
+    this.prefs = Services.prefs.getBranch("messenger.account." +
+                                          this.account.id + ".options.");
+    this.populateProtoSpecificBox();
+  },
+
+  createTextbox: function account_createTextbox(aType, aLabel, aName) {
+    var box = document.createElement("vbox");
+
+    var label = document.createElement("label");
+    label.setAttribute("value", aLabel);
+    label.setAttribute("control", aName);
+    box.appendChild(label);
+
+    var textbox = document.createElement("textbox");
+    if (aType)
+      textbox.setAttribute("type", aType);
+    textbox.setAttribute("preftype", aType == "number" ? "int" : "wstring");
+    textbox.setAttribute("id", aName);
+    textbox.setAttribute("wsm_persist", "true");
+    textbox.setAttribute("genericattr", "true");
+
+    box.appendChild(textbox);
+    return box;
+  },
+
+  createMenulist: function account_createMenulist(aList, aLabel, aName) {
+    var box = document.createElement("hbox");
+    box.setAttribute("align", "baseline");
+
+    var label = document.createElement("label");
+    label.setAttribute("value", aLabel);
+    label.setAttribute("control", aName);
+    box.appendChild(label);
+
+    aList.QueryInterface(Ci.nsISimpleEnumerator);
+    var menulist = document.createElement("menulist");
+    menulist.setAttribute("id", aName);
+    menulist.setAttribute("wsm_persist", "true");
+    menulist.setAttribute("preftype", "wstring");
+    menulist.setAttribute("genericattr", "true");
+    var popup = menulist.appendChild(document.createElement("menupopup"));
+    while (aList.hasMoreElements()) {
+      let elt = aList.getNext();
+      let item = document.createElement("menuitem");
+      item.setAttribute("label", elt.name);
+      item.setAttribute("value", elt.value);
+      popup.appendChild(item);
+    }
+    box.appendChild(menulist);
+    return box;
+  },
+
+  populateProtoSpecificBox: function account_populate() {
+    var gbox = document.getElementById("protoSpecific");
+    let child;
+    while ((child = gbox.firstChild))
+      gbox.removeChild(child);
+
+    let options = this.proto.getOptions();
+    while (options.hasMoreElements()) {
+      let opt = options.getNext();
+      var text = opt.label;
+      var name = "server." + opt.name;
+      switch (opt.type) {
+      case opt.typeBool:
+        var chk = document.createElement("checkbox");
+        chk.setAttribute("label", text);
+        chk.setAttribute("id", name);
+        chk.setAttribute("wsm_persist", "true");
+        chk.setAttribute("preftype", "bool");
+        chk.setAttribute("genericattr", "true");
+
+        gbox.appendChild(chk);
+        break;
+      case opt.typeInt:
+        gbox.appendChild(this.createTextbox("number", text, name));
+        break;
+      case opt.typeString:
+        gbox.appendChild(this.createTextbox(null, text, name));
+        break;
+      case opt.typeList:
+        gbox.appendChild(this.createMenulist(opt.getList(), text, name));
+        break;
+      default:
+        throw "unknown preference type " + opt.type;
+      }
+    }
+
+    let advanced = document.getElementById("advanced");
+    if (advanced.hidden && gbox.firstChild) {
+      advanced.hidden = false;
+      // Force textbox XBL binding attachment by forcing layout,
+      // otherwise setFormElementValue from AccountManager.js sets
+      // properties that don't exist when restoring values.
+      gbox.getBoundingClientRect();
+    }
+    else if (!gbox.firstChild)
+      advanced.hidden = true;
+  }
+};
new file mode 100644
--- /dev/null
+++ b/mail/components/im/content/am-im.xul
@@ -0,0 +1,84 @@
+<?xml version="1.0"?>
+<!-- ***** BEGIN LICENSE BLOCK *****
+   - Version: MPL 1.1/GPL 2.0/LGPL 2.1
+   -
+   - The contents of this file are subject to the Mozilla Public License Version
+   - 1.1 (the "License"); you may not use this file except in compliance with
+   - the License. You may obtain a copy of the License at
+   - http://www.mozilla.org/MPL/
+   -
+   - Software distributed under the License is distributed on an "AS IS" basis,
+   - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+   - for the specific language governing rights and limitations under the
+   - License.
+   -
+   - The Original Code is the Instantbird messenging client, released
+   - 2007.
+   -
+   - The Initial Developer of the Original Code is
+   - Florian QUEZE <florian@instantbird.org>.
+   - Portions created by the Initial Developer are Copyright (C) 2007
+   - the Initial Developer. All Rights Reserved.
+   -
+   - Contributor(s):
+   -
+   - Alternatively, the contents of this file may be used under the terms of
+   - either the GNU General Public License Version 2 or later (the "GPL"), or
+   - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+   - in which case the provisions of the GPL or the LGPL are applicable instead
+   - of those above. If you wish to allow use of your version of this file only
+   - under the terms of either the GPL or the LGPL, and not to allow others to
+   - use your version of this file under the terms of the MPL, indicate your
+   - decision by deleting the provisions above and replace them with the notice
+   - and other provisions required by the GPL or the LGPL. If you do not delete
+   - the provisions above, a recipient may use your version of this file under
+   - the terms of any one of the MPL, the GPL or the LGPL.
+   -
+   - ***** END LICENSE BLOCK ***** -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<!DOCTYPE window SYSTEM "chrome://messenger/locale/am-im.dtd">
+
+<page
+  id     = "account"
+  title  = "&accountWindow.title;"
+  buttons= "accept,cancel"
+  xmlns  = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+  onload = "parent.onPanelLoaded('am-im.xul');">
+  <script type="application/javascript" src="chrome://messenger/content/am-im.js"/>
+
+  <hbox>
+    <image id="protocolIcon"/>
+    <vbox flex="1">
+      <label id="accountName" crop="end" class="header"/>
+      <label id="protocolName"/>
+    </vbox>
+  </hbox>
+  <separator/>
+
+  <hbox id="passwordBox" equalsize="always" align="baseline">
+    <label value="&account.password;" control="password" flex="1"/>
+    <textbox id="server.password" flex="1" type="password"
+             preftype="wstring" genericattr="true"/>
+  </hbox>
+  <separator class="groove"/>
+
+  <hbox id="aliasBox" equalsize="always" align="baseline">
+    <label value="&account.alias;" control="alias" flex="1"/>
+    <textbox id="server.alias" flex="1" preftype="wstring"
+             wsm_persist="true" genericattr="true"/>
+  </hbox>
+
+  <separator class="groove" hidden="true" id="optionalSeparator"/>
+
+  <vbox id="autojoinBox" hidden="true">
+    <label value="&account.autojoin;" control="autojoin" flex="1"/>
+    <textbox id="server.autojoin" flex="1" preftype="wstring" genericattr="true"/>
+  </vbox>
+
+  <groupbox id="advanced">
+    <caption label="&account.advanced;"/>
+    <vbox id="protoSpecific" flex="1"/>
+  </groupbox>
+</page>
new file mode 100644
--- /dev/null
+++ b/mail/components/im/content/chat-messenger-overlay.js
@@ -0,0 +1,692 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var imServices = {};
+Components.utils.import("resource:///modules/imServices.jsm", imServices);
+imServices = imServices.Services;
+
+Components.utils.import("resource:///modules/index_im.js");
+Components.utils.import("resource://gre/modules/FileUtils.jsm");
+
+var gBuddyListContextMenu = null;
+
+function buddyListContextMenu(aXulMenu) {
+  this.target = aXulMenu.triggerNode;
+  this.menu = aXulMenu;
+  let localName = this.target.localName;
+  this.onContact = localName == "imcontact";
+  this.onConv = localName == "imconv";
+  this.shouldDisplay = this.onContact || this.onConv;
+
+  let hide = !this.onContact;
+  ["context-openconversation", "context-edit-buddy-separator",
+    "context-alias", "context-delete"].forEach(function(aId) {
+    document.getElementById(aId).hidden = hide;
+  });
+
+  document.getElementById("context-close-conversation").hidden = !this.onConv;
+  document.getElementById("context-openconversation").disabled =
+    !hide && !this.target.canOpenConversation();
+}
+buddyListContextMenu.prototype = {
+  openConversation: function blcm_openConversation() {
+    if (this.onContact || this.onConv)
+      this.target.openConversation();
+  },
+  closeConversation: function blcm_closeConversation() {
+    if (this.onConv)
+      this.target.closeConversation();
+  },
+  alias: function blcm_alias() {
+    if (this.onContact)
+      this.target.startAliasing();
+  },
+  delete: function blcm_delete() {
+    if (!this.onContact)
+      return;
+
+    let buddy = this.target.contact.preferredBuddy;
+    let bundle = document.getElementById("chatBundle");
+    let displayName = this.target.displayName;
+    let promptTitle = bundle.getFormattedString("buddy.deletePrompt.title",
+                                                [displayName]);
+    let userName = buddy.userName;
+    if (displayName != userName) {
+      displayName = bundle.getFormattedString("buddy.deletePrompt.displayName",
+                                              [displayName, userName]);
+    }
+    let proto = buddy.protocol.name; // FIXME build a list
+    let promptMessage = bundle.getFormattedString("buddy.deletePrompt.message",
+                                                  [displayName, proto]);
+    let deleteButton = bundle.getString("buddy.deletePrompt.button");
+    let prompts = Services.prompt;
+    let flags = prompts.BUTTON_TITLE_IS_STRING * prompts.BUTTON_POS_0 +
+                prompts.BUTTON_TITLE_CANCEL * prompts.BUTTON_POS_1 +
+                prompts.BUTTON_POS_1_DEFAULT;
+    if (prompts.confirmEx(window, promptTitle, promptMessage, flags,
+                          deleteButton, null, null, null, {}))
+      return;
+
+    this.target.remove();
+  }
+};
+
+var gChatTab = null;
+
+var chatTabType = {
+  name: "chat",
+  panelId: "chatTabPanel",
+  modes: {
+    chat: {
+      type: "chat"
+    }
+  },
+
+  _handleArgs: function(aArgs) {
+    if (!aArgs || !("convType" in aArgs) || aArgs.convType != "log")
+      return;
+
+    let item = document.getElementById("searchResultConv");
+    item.log = aArgs.conv;
+    item.hidden = false;
+    document.getElementById("contactlistbox").selectedItem = item;
+  },
+  openTab: function(aTab, aArgs) {
+    if (!document.getElementById("conversationsGroup").nextSibling.localName == "imconv") {
+      let convs = imServices.conversations.getUIConversations();
+      if (convs.length != 0) {
+        convs.sort(function(a, b)
+                   a.title.toLowerCase().localeCompare(b.title.toLowerCase()));
+        for each (let conv in convs)
+          chatHandler._addConversation(conv);
+      }
+    }
+
+    gChatTab = aTab;
+    aTab.tabNode.setAttribute("image", "chrome://chat/skin/chat-16.png");
+    this._handleArgs(aArgs);
+    chatHandler.updateTitle();
+  },
+  shouldSwitchTo: function(aArgs) {
+    if (!gChatTab)
+      return -1;
+    this._handleArgs(aArgs);
+    return document.getElementById("tabmail").tabInfo.indexOf(gChatTab);
+  },
+  showTab: function(aTab) {
+    gChatTab = aTab;
+    let list = document.getElementById("contactlistbox");
+    let convs = document.getElementById("conversationsGroup");
+    if (!list.selectedItem || list.selectedItem == convs) {
+      list.selectedItem =
+        convs.nextSibling.localName == "imconv" ? convs.nextSibling : convs;
+    }
+    else
+      chatHandler.onListItemSelected();
+  },
+  closeTab: function(aTab) {
+    gChatTab = null;
+  },
+
+  supportsCommand: function (aCommand, aTab) false,
+  isCommandEnabled: function (aCommand, aTab) false,
+  doCommand: function(aCommand, aTab) { },
+  onEvent: function(aEvent, aTab) { },
+
+  saveTabState: function(aTab) { }
+};
+
+var chatHandler = {
+  _addConversation: function(aConv) {
+    let list = document.getElementById("contactlistbox");
+    let convs = document.getElementById("conversationsGroup");
+    let selectedItem = list.selectedItem;
+    let shouldSelect =
+      gChatTab && gChatTab.tabNode.selected &&
+      (!selectedItem || (selectedItem == convs &&
+                        convs.nextSibling.localName != "imconv"));
+    let elt = convs.addContact(aConv, "imconv");
+    if (shouldSelect)
+      list.selectedItem = elt;
+    return elt;
+  },
+
+  updateTitle: function() {
+    let list = document.getElementById("conversationsGroup");
+    let unreadCount = 0;
+    for (let node = list.nextSibling; node.localName == "imconv"; node = node.nextSibling) {
+      let conv = node.conv;
+      if (!conv)
+        continue;
+      if (conv.isChat)
+        unreadCount += conv.unreadTargetedMessageCount;
+      else
+        unreadCount += conv.unreadIncomingMessageCount;
+    }
+    
+    let title =
+      document.getElementById("chatBundle").getString("chatTabTitle");
+    if (unreadCount)
+      title += " (" + unreadCount + ")";
+    let selectedItem = document.getElementById("contactlistbox").selectedItem;
+    if (selectedItem && selectedItem.localName == "imconv")
+      title += " - " + selectedItem.getAttribute("displayname");
+    gChatTab.title = title;
+    document.getElementById("tabmail").setTabTitle(gChatTab);
+  },
+
+  onConvResize: function() {
+    let convDeck = document.getElementById("conversationsDeck");
+    let panel = convDeck.selectedPanel;
+    if (panel && panel.localName == "imconversation")
+      panel.onConvResize();
+  },
+
+  setStatusMenupopupCommand: function(aEvent) {
+    let target = aEvent.originalTarget;
+    if (target.getAttribute("id") == "imStatusShowAccounts") {
+      openIMAccountMgr();
+      return;
+    }
+
+    let status = target.getAttribute("status");
+    if (!status)
+      return; // Can status really be null? Maybe because of an add-on...
+
+    let us = imServices.core.globalUserStatus;
+    us.setStatus(Status.toFlag(status), us.statusText);
+  },
+
+  _pendingLogBrowserLoad: false,
+  _showLog: function(aLog) {
+    document.getElementById("conversationsDeck").selectedPanel =
+      document.getElementById("logDisplay");
+    document.getElementById("logDisplayDeck").selectedPanel =
+      document.getElementById("logDisplayBrowserBox");
+    let conv = aLog.getConversation();
+    let browser = document.getElementById("conv-log-browser");
+    if (this._pendingLogBrowserLoad) {
+      browser._conv = conv;
+      return conv;
+    }
+    browser.init(conv);
+    this._pendingLogBrowserLoad = true;
+    Services.obs.addObserver(this, "conversation-loaded", false);
+    return conv;
+  },
+  _makeFriendlyDate: function(aDate) {
+    let dts = Components.classes["@mozilla.org/intl/scriptabledateformat;1"]
+                        .getService(Components.interfaces.nsIScriptableDateFormat);
+
+    // Figure out when today begins
+    let now = new Date();
+    let today = new Date(now.getFullYear(), now.getMonth(),
+                         now.getDate());
+
+    // Figure out if the end time is from today, yesterday,
+    // this week, etc.
+    let kDayInMsecs = 24 * 60 * 60 * 1000;
+    let time = dts.FormatTime("", dts.timeFormatNoSeconds,
+                              aDate.getHours(), aDate.getMinutes(),0);
+    let bundle = document.getElementById("chatBundle");
+    if (aDate >= today)
+      return bundle.getFormattedString("today", [time]);
+    if (today - aDate < kDayInMsecs)
+      return bundle.getFormattedString("yesterday", [time]);
+
+    let date = dts.FormatDate("", dts.dateFormatShort, aDate.getFullYear(),
+                              aDate.getMonth() + 1, aDate.getDate());
+    return bundle.getFormattedString("dateTime", [date, time]);
+  },
+
+  _showLogList: function(aLogs) {
+    let listbox = document.getElementById("logList");
+    while (listbox.firstChild)
+      listbox.removeChild(listbox.firstChild);
+    let logs = [];
+    for (let log in fixIterator(aLogs))
+      logs.push(log);
+    logs.sort(function(log1, log2) log2.time - log1.time);
+    for each (let log in logs) {
+      let elt = document.createElement("listitem");
+      elt.setAttribute("label",
+                       this._makeFriendlyDate(new Date(log.time * 1000)));
+      elt.log = log;
+      listbox.appendChild(elt);
+    }
+    return logs;
+  },
+  onLogSelect: function() {
+    let selectedItem = document.getElementById("logList").selectedItem;
+    if (!selectedItem)
+      return;
+    let log = selectedItem.log;
+    if (!log) {
+      let item = document.getElementById("contactlistbox").selectedItem;
+      if (item) {
+        document.getElementById("conversationsDeck").selectedPanel =
+          item.convView;
+      }
+      return;
+    }
+
+    let list = document.getElementById("contactlistbox");
+    if (list.selectedItem.getAttribute("id") != "searchResultConv")
+      document.getElementById("goToConversation").hidden = false;
+    this._showLog(log);
+  },
+
+  _contactObserver: {
+    observe: function(aSubject, aTopic, aData) {
+      if (aTopic == "contact-status-changed" ||
+          aTopic == "contact-display-name-changed" ||
+          aTopic == "contact-icon-changed")
+        chatHandler.showContactInfo(aSubject);
+    }
+  },
+  _observedContact: null,
+  get observedContact() this._observedContact,
+  set observedContact(aContact) {
+    if (aContact == this._observedContact)
+      return;
+    if (this._observedContact) {
+      this._observedContact.removeObserver(this._contactObserver);
+      delete this._observedContact;
+    }
+    this._observedContact = aContact;
+    if (aContact)
+      aContact.addObserver(this._contactObserver);
+    return aContact;
+  },
+  showCurrentConversation: function() {
+    let item = document.getElementById("contactlistbox").selectedItem;
+    if (!item)
+      return;
+    if (item.localName == "imconv")
+      document.getElementById("conversationsDeck").selectedPanel = item.convView;
+    else if (item.localName == "imcontact")
+      item.openConversation();
+  },
+  showContactInfo: function(aContact) {
+    let cti = document.getElementById("conv-top-info");
+    cti.setAttribute("userIcon", aContact.buddyIconFilename);
+    cti.setAttribute("displayName", aContact.displayName);
+    let statusText = aContact.statusText;
+    let statusType = aContact.statusType;
+    if (statusText)
+      statusText = " - " + statusText;
+    cti.setAttribute("statusMessageWithDash", statusText);
+    let statusString = Status.toLabel(statusType);
+    cti.setAttribute("statusMessage", statusString + statusText);
+    cti.setAttribute("status", Status.toAttribute(statusType));
+    cti.setAttribute("statusTypeTooltiptext", statusString);
+    cti.removeAttribute("typing");
+    cti.removeAttribute("typed");
+
+    let bundle = document.getElementById("chatBundle");
+    let button = document.getElementById("goToConversation");
+    button.label = bundle.getFormattedString("startAConversationWith.button",
+                                             [aContact.displayName]);
+    button.disabled = !aContact.canSendMessage;
+  },
+  _hideContextPane: function(aHide) {
+    document.getElementById("contextSplitter").hidden = aHide;
+    document.getElementById("contextPane").hidden = aHide;
+  },
+  onListItemSelected: function() {
+    let item = document.getElementById("contactlistbox").selectedItem;
+    if (!item || item.localName == "imgroup") {
+      this._hideContextPane(true);
+      document.getElementById("conversationsDeck").selectedPanel =
+        document.getElementById("noConvScreen");
+      this.updateTitle();
+      this.observedContact = null;
+      return;
+    }
+    if (item.getAttribute("id") == "searchResultConv") {
+      let path = "logs/" + item.log.path;
+      let file = FileUtils.getFile("ProfD", path.split("/"));
+      let log = imServices.logs.getLogFromFile(file);
+      let button = document.getElementById("goToConversation");
+      button.hidden = true;
+      let conv = this._showLog(log);
+
+      let cti = document.getElementById("conv-top-info");
+      cti.setAttribute("displayName", conv.title);
+      cti.removeAttribute("userIcon");
+      cti.removeAttribute("statusMessageWithDash");
+      cti.removeAttribute("statusMessage");
+      cti.removeAttribute("status");
+      cti.removeAttribute("statusTypeTooltiptext");
+
+      let logs = this._showLogList(imServices.logs.getSimilarLogs(log));
+      let time = log.time;
+      let list = document.getElementById("logList");
+      let logItem = list.firstChild;
+      while (logItem) {
+        if (logItem.log.time == time) {
+          list.selectedItem = logItem;
+          break;
+        }
+        logItem = logItem.nextSibling;
+      }
+      this.observedContact = null;
+    }
+    else if (item.localName == "imconv") {
+      let convDeck = document.getElementById("conversationsDeck");
+      if (!item.convView) {
+        let conv = document.createElement("imconversation");
+        convDeck.appendChild(conv);
+        conv.conv = item.conv;
+        conv.tab = item;
+        conv.setAttribute("contentcontextmenu", "chatConversationContextMenu");
+        item.convView = conv;
+        document.getElementById("contextSplitter").hidden = false;
+        document.getElementById("contextPane").hidden = false;
+      }
+      else
+        item.convView.onConvResize();
+      convDeck.selectedPanel = item.convView;
+      item.convView.updateConvStatus();
+      item.update();
+
+      this._showLogList(imServices.logs.getLogsForConversation(item.conv));
+      let contextPane = document.getElementById("contextPane");
+      if (item.conv.isChat) {
+        contextPane.setAttribute("chat", "true");
+        item.convView.showParticipants();        
+      }
+      else
+        contextPane.removeAttribute("chat");
+
+      let button = document.getElementById("goToConversation");
+      let bundle = document.getElementById("chatBundle");
+      button.label = bundle.getString("goBackToCurrentConversation.button");
+      button.disabled = false;
+      this.observedContact = null;
+    }
+    else if (item.localName == "imcontact") {
+      let contact = item.contact;
+      if (this.observedContact && contact &&
+          this.observedContact.id == contact.id)
+        return; // onselect has just been fired again because a status
+                // change caused the imcontact to move.
+                // Return early to avoid flickering and changing the selected log.
+
+      this.showContactInfo(contact);
+      this.observedContact = contact;
+
+      document.getElementById("contextPane").removeAttribute("chat");
+
+      let logs = this._showLogList(imServices.logs.getLogsForContact(contact));
+      let listbox = document.getElementById("logList");
+      if (logs.length)
+        listbox.selectedItem = listbox.firstChild;
+      else {
+        document.getElementById("conversationsDeck").selectedPanel =
+          document.getElementById("logDisplay");
+        document.getElementById("logDisplayDeck").selectedPanel =
+          document.getElementById("noPreviousConvScreen");
+      }
+    }
+    this._hideContextPane(false);
+    this.updateTitle();
+  },
+
+  _openDialog: function(aType) {
+    let features = "chrome,modal,titlebar,centerscreen";
+    window.openDialog("chrome://messenger/content/chat/" + aType + ".xul", "",
+                      features);
+  },
+  addBuddy: function() {
+     this._openDialog("addbuddy");
+  },
+  joinChat: function() {
+    this._openDialog("joinchat");
+  },
+
+  _colorCache: {},
+  // Duplicated code from imconversation.xml :-(
+  _computeColor: function(aName) {
+    if (Object.prototype.hasOwnProperty.call(this._colorCache, aName))
+      return this._colorCache[aName];
+
+    // Compute the color based on the nick
+    var nick = aName.match(/[a-zA-Z0-9]+/);
+    nick = nick ? nick[0].toLowerCase() : nick = aName;
+    // We compute a hue value (between 0 and 359) based on the
+    // characters of the nick.
+    // The first character weights kInitialWeight, each following
+    // character weights kWeightReductionPerChar * the weight of the
+    // previous character.
+    const kInitialWeight = 10; // 10 = 360 hue values / 36 possible characters.
+    const kWeightReductionPerChar = 0.52; // arbitrary value
+    var weight = kInitialWeight;
+    var res = 0;
+    for (var i = 0; i < nick.length; ++i) {
+      var char = nick.charCodeAt(i) - 47;
+      if (char > 10)
+        char -= 39;
+      // now char contains a value between 1 and 36
+      res += char * weight;
+      weight *= kWeightReductionPerChar;
+    }
+    return (this._colorCache[aName] = Math.round(res) % 360);
+  },
+
+  _updateNoConvPlaceHolder: function() {
+    let connected = false;
+    let hasAccount = false;
+    for (let account in fixIterator(imServices.accounts.getAccounts())) {
+      hasAccount = true;
+      if (account.connected) {
+        connected = true;
+        break;
+      }
+    }
+    document.getElementById("noConvInnerBox").hidden = !connected;
+    document.getElementById("noAccountInnerBox").hidden = hasAccount;
+    document.getElementById("noConnectedAccountInnerBox").hidden =
+      connected || !hasAccount;
+  },
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic == "browser-request") {
+      imServices.ww.openWindow(null,
+                               "chrome://chat/content/browserRequest.xul",
+                               null, "chrome", aSubject);
+      return;
+    }
+
+    if (aTopic == "conversation-loaded") {
+      let browser = document.getElementById("conv-log-browser");
+      if (aSubject != browser)
+        return;
+
+      for each (let msg in browser._conv.getMessages()) {
+        if (!msg.system)
+          msg.color = "color: hsl(" + this._computeColor(msg.who) + ", 100%, 40%);";
+        browser.appendMessage(msg);
+      }
+
+      delete this._pendingLogBrowserLoad;
+      Services.obs.removeObserver(this, "conversation-loaded");
+      return;      
+    }
+
+    if (aTopic == "account-connected" || aTopic == "account-disconnected" ||
+        aTopic == "account-added" || aTopic == "account-removed") {
+      this._updateNoConvPlaceHolder();
+      return;
+    }
+
+    if (aTopic == "contact-signed-on") {
+      document.getElementById("onlinecontactsGroup").addContact(aSubject);
+      document.getElementById("offlinecontactsGroup").removeContact(aSubject);
+      return;
+    }
+    if (aTopic == "contact-signed-off") {
+      document.getElementById("offlinecontactsGroup").addContact(aSubject);
+      document.getElementById("onlinecontactsGroup").removeContact(aSubject);
+      return;
+    }
+    if (aTopic == "contact-added") {
+      let groupName = (aSubject.online ? "on" : "off") + "linecontactsGroup";
+      document.getElementById(groupName).addContact(aSubject);
+      return;
+    }
+    if (aTopic == "contact-removed") {
+      let groupName = (aSubject.online ? "on" : "off") + "linecontactsGroup";
+      document.getElementById(groupName).removeContact(aSubject);
+      return;
+    }
+    if (aTopic == "contact-no-longer-dummy") {
+      let oldId = parseInt(aData);
+      let groupName = (aSubject.online ? "on" : "off") + "linecontactsGroup";
+      let group = document.getElementById(groupName);
+      if (group.contactsById.hasOwnProperty(oldId)) {
+        let contact = group.contactsById[oldId];
+        delete group.contactsById[oldId];
+        group.contactsById[contact.contact.id] = contact;
+      }
+      return;
+    }
+    if (aTopic == "new-ui-conversation") {
+      if (!gChatTab) {
+        let tabmail = document.getElementById("tabmail");
+        tabmail.openTab("chat", {background: true, convType: "new",
+                                 conv: aSubject});
+      }
+      let conv = chatHandler._addConversation(aSubject);
+      if (!aSubject.isChat && aSubject.buddy) {
+        let contact = aSubject.buddy.buddy.contact;
+        conv.imContact = contact;
+        let groupName = (contact.online ? "on" : "off") + "linecontactsGroup";
+        let item = document.getElementById(groupName).removeContact(contact);
+        let list = document.getElementById("contactlistbox");
+        if (list.selectedItem == item)
+          list.selectedItem = conv;
+      }
+      return;
+    }
+    if (aTopic == "ui-conversation-closed") {
+      let conv =
+        document.getElementById("conversationsGroup").removeContact(aSubject);
+      if (conv.imContact) {
+        let contact = conv.imContact;
+        let groupName = (contact.online ? "on" : "off") + "linecontactsGroup";
+        document.getElementById(groupName).addContact(contact);
+      }
+      return;
+    }
+
+    if (aTopic == "buddy-authorization-request") {
+      aSubject.QueryInterface(Ci.prplIBuddyRequest);
+      let bundle = document.getElementById("chatBundle");
+      let label = bundle.getFormattedString("buddy.authRequest.label",
+                                            [aSubject.userName]);
+      let value =
+        "buddy-auth-request-" + aSubject.account.id + aSubject.userName;
+      let acceptButton = {
+        accessKey: bundle.getString("buddy.authRequest.allow.accesskey"),
+        label: bundle.getString("buddy.authRequest.allow.label"),
+        callback: function() { aSubject.grant(); }
+      };
+      let denyButton = {
+        accessKey: bundle.getString("buddy.authRequest.deny.accesskey"),
+        label: bundle.getString("buddy.authRequest.deny.label"),
+        callback: function() { aSubject.deny(); }
+      };
+      let box = document.getElementById("chatTabPanel");
+      box.appendNotification(label, value, null, box.PRIORITY_INFO_HIGH,
+                            [acceptButton, denyButton]);
+      if (!gChatTab) {
+        let tabmail = document.getElementById("tabmail");
+        tabmail.openTab("chat", {background: true});
+      }
+      return;
+    }
+    if (aTopic == "buddy-authorization-request-canceled") {
+      aSubject.QueryInterface(Ci.prplIBuddyRequest);
+      let value =
+        "buddy-auth-request-" + aSubject.account.id + aSubject.userName;
+      let notification =
+        document.getElementById("chatTabPanel")
+                .getNotificationWithValue(value);
+      if (notification)
+        notification.close();
+      return;
+    }
+  },
+  initContactList: function() {
+    let group = document.getElementById("offlinecontactsGroup");
+    imServices.tags.getTags().forEach(function (aTag) {
+      aTag.getContacts().forEach(function(aContact) {
+        group.addContact(aContact);
+      });
+    });
+    group._updateGroupLabel();
+    imServices.obs.addObserver(this, "new-ui-conversation", false);
+    imServices.obs.addObserver(this, "ui-conversation-closed", false);
+    imServices.obs.addObserver(this, "contact-signed-on", false);
+    imServices.obs.addObserver(this, "contact-signed-off", false);
+    imServices.obs.addObserver(this, "contact-added", false);
+    imServices.obs.addObserver(this, "contact-removed", false);
+    imServices.obs.addObserver(this, "contact-no-longer-dummy", false);
+    imServices.obs.addObserver(this, "account-connected", false);
+    imServices.obs.addObserver(this, "account-disconnected", false);
+    imServices.obs.addObserver(this, "account-added", false);
+    imServices.obs.addObserver(this, "account-removed", false);
+  },
+  init: function() {
+    if (!Services.prefs.getBoolPref("mail.chat.enabled")) {
+      ["button-chat", "menu_goChat", "goChatSeparator",
+       "imAccountsStatus", "newIMAccountMenuItem"].forEach(function(aId) {
+         document.getElementById(aId).hidden = true;
+       });
+      document.getElementById("key_goChat").disabled = true;
+      return;
+    }
+
+    // initialize the customizeDone method on the customizeable toolbar
+    var toolbox = document.getElementById("chat-view-toolbox");
+    toolbox.customizeDone = function(aEvent) {
+      MailToolboxCustomizeDone(aEvent, "CustomizeChatToolbar");
+    };
+
+    let tabmail = document.getElementById("tabmail");
+    tabmail.registerTabType(chatTabType);
+    imServices.obs.addObserver(this, "browser-request", false);
+    imServices.obs.addObserver(this, "buddy-authorization-request", false);
+    imServices.obs.addObserver(this, "buddy-authorization-request-canceled", false);
+    let listbox = document.getElementById("contactlistbox");
+    listbox.addEventListener("keypress", function(aEvent) {
+      let item = listbox.selectedItem;
+      if (!item || !item.parentNode) // empty list or item no longer in the list
+        return;
+      item.keyPress(aEvent);
+    });
+    listbox.addEventListener("select", this.onListItemSelected.bind(this));
+    window.addEventListener("resize", this.onConvResize.bind(this));
+    document.getElementById("conversationsGroup").sortComparator =
+      function(a, b) a.title.toLowerCase().localeCompare(b.title.toLowerCase());
+
+    // The initialization of the im core may trigger a master password prompt,
+    // so wrap it with the async prompter service.
+    Components.classes["@mozilla.org/messenger/msgAsyncPrompter;1"]
+              .getService(Components.interfaces.nsIMsgAsyncPrompter)
+              .queueAsyncAuthPrompt("im", false, {
+      onPromptStart: function() {
+        imServices.core.init();
+        chatHandler.initContactList();
+        chatHandler._updateNoConvPlaceHolder();
+        statusSelector.init();
+        return true;
+      },
+      onPromptAuthAvailable : function() { },
+      onPromptCanceled : function() { }
+    });
+  }
+};
+
+window.addEventListener("load", chatHandler.init.bind(chatHandler));
new file mode 100644
--- /dev/null
+++ b/mail/components/im/content/chat-messenger-overlay.xul
@@ -0,0 +1,299 @@
+<?xml version="1.0"?>
+<!-- ***** BEGIN LICENSE BLOCK *****
+   - Version: MPL 1.1/GPL 2.0/LGPL 2.1
+   -
+   - The contents of this file are subject to the Mozilla Public License Version
+   - 1.1 (the "License"); you may not use this file except in compliance with
+   - the License. You may obtain a copy of the License at
+   - http://www.mozilla.org/MPL/
+   -
+   - Software distributed under the License is distributed on an "AS IS" basis,
+   - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+   - for the specific language governing rights and limitations under the
+   - License.
+   -
+   - The Original Code is Mozilla Thunderbird.
+   -
+   - The Initial Developer of the Original Code is
+   - Florian QUEZE <florian@queze.net>.
+   - Portions created by the Initial Developer are Copyright (C) 2011
+   - the Initial Developer. All Rights Reserved.
+   -
+   - Contributor(s):
+   -
+   - Alternatively, the contents of this file may be used under the terms of
+   - either the GNU General Public License Version 2 or later (the "GPL"), or
+   - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+   - in which case the provisions of the GPL or the LGPL are applicable instead
+   - of those above. If you wish to allow use of your version of this file only
+   - under the terms of either the GPL or the LGPL, and not to allow others to
+   - use your version of this file under the terms of the MPL, indicate your
+   - decision by deleting the provisions above and replace them with the notice
+   - and other provisions required by the GPL or the LGPL. If you do not delete
+   - the provisions above, a recipient may use your version of this file under
+   - the terms of any one of the MPL, the GPL or the LGPL.
+   -
+   - ***** END LICENSE BLOCK ***** -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/content/chat/chat.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/chat.css" type="text/css"?>
+
+<!DOCTYPE overlay [
+  <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+  %brandDTD;
+  <!ENTITY % chatDTD SYSTEM "chrome://messenger/locale/chat.dtd">
+  %chatDTD;
+  <!ENTITY % messengerDTD SYSTEM "chrome://messenger/locale/messenger.dtd">
+  %messengerDTD;
+  <!ENTITY % textcontextDTD SYSTEM "chrome://global/locale/textcontext.dtd">
+  %textcontextDTD;
+]>
+
+<overlay id="chat-messenger-overlay"
+         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+  <script type="application/javascript"
+          src="chrome://messenger/content/chat/chat-messenger-overlay.js"/>
+  <script type="application/javascript"
+          src="chrome://messenger/content/chat/imStatusSelector.js"/>
+  <script type="application/javascript"
+          src="chrome://messenger/content/chat/imContextMenu.js"/>
+
+  <stringbundleset id="stringbundleset">
+    <stringbundle id="chatBundle" src="chrome://messenger/locale/chat.properties"/>
+  </stringbundleset>
+
+  <popupset id="mainPopupSet">
+    <tooltip id="buddyTooltip" type="buddy"/>
+
+    <menupopup id="buddyListContextMenu"
+               onpopupshowing="if (event.target != this) return true; gBuddyListContextMenu = new buddyListContextMenu(this); return gBuddyListContextMenu.shouldDisplay;"
+               onpopuphiding="if (event.target == this) { gBuddyListContextMenu = null; }">
+      <menuitem id="context-openconversation"
+                label="&openConversationCmd.label;"
+                accesskey="&openConversationCmd.accesskey;"
+                oncommand="gBuddyListContextMenu.openConversation();"/>
+      <menuitem id="context-close-conversation"
+                label="&closeConversationCmd.label;"
+                accesskey="&closeConversationCmd.accesskey;"
+                oncommand="gBuddyListContextMenu.closeConversation();"/>
+      <menuseparator id="context-edit-buddy-separator"/>
+      <menuitem id="context-alias"
+                label="&aliasCmd.label;"
+                accesskey="&aliasCmd.accesskey;"
+                oncommand="gBuddyListContextMenu.alias();"/>
+      <menuitem id="context-delete"
+                label="&deleteCmd.label;"
+                accesskey="&deleteCmd.accesskey;"
+                oncommand="gBuddyListContextMenu.delete();"/>
+    </menupopup>
+
+    <menupopup id="chatConversationContextMenu"
+               onpopupshowing="if (event.target != this) return true; gChatContextMenu = new imContextMenu(this); return gChatContextMenu.shouldDisplay;"
+               onpopuphiding="if (event.target == this &amp;&amp; gChatContextMenu) { gChatContextMenu.cleanup(); gChatContextMenu = null; }">
+      <menuitem id="context-openlink"
+                label="&openLinkCmd.label;"
+                accesskey="&openLinkCmd.accesskey;"
+                oncommand="gChatContextMenu.openLink();"/>
+      <menuitem id="context-copyemail"
+                label="&copyEmailCmd.label;"
+                accesskey="&copyEmailCmd.accesskey;"
+                oncommand="gChatContextMenu.copyEmail();"/>
+      <menuitem id="context-copylink"
+                label="&copyLinkCmd.label;"
+                accesskey="&copyLinkCmd.accesskey;"
+                oncommand="goDoCommand('cmd_copyLink');"/>
+      <menuseparator id="context-sep-copylink"/>
+
+      <menuitem id="context-copy"
+                label="&copyCmd.label;"
+                accesskey="&copyCmd.accesskey;"
+                command="cmd_copy"/>
+      <menuitem id="context-selectall"
+                label="&selectAllCmd.label;"
+                accesskey="&selectAllCmd.accesskey;"
+                command="cmd_selectAll"/>
+      <menuseparator id="context-sep-messageactions"/>
+    </menupopup>
+
+    <menupopup id="chat-toolbar-context-menu">
+      <menuitem id="CustomizeChatToolbar"
+                oncommand="CustomizeMailToolbar('chat-view-toolbox', 'CustomizeChatToolbar')"
+                label="&customizeToolbar.label;"
+                accesskey="&customizeToolbar.accesskey;"/>
+    </menupopup>
+  </popupset>
+
+  <tabpanels id="tabpanelcontainer">
+    <vbox>
+      <toolbox id="chat-view-toolbox" class="mail-toolbox"
+               labelalign="end" defaultlabelalign="end">
+        <toolbar class="inline-toolbar chromeclass-toolbar"
+                 id="chat-toobar"
+                 fullscreentoolbar="true"
+                 mode="full"
+                 defaultmode="full"
+                 customizable="true"
+                 context="chat-toolbar-context-menu"
+#ifdef XP_MACOSX
+                 iconsize="small"
+#endif
+                 defaultset="button-add-buddy,button-join-chat,spacer,chat-status-selector,button-chat-accounts,spacer,gloda-im-search"/>
+
+        <toolbarpalette id="ChatToolbarPalette">
+          <toolbarbutton id="button-add-buddy"
+                         class="toolbarbutton-1"
+                         label="&addBuddyButton.label;"
+                         oncommand="chatHandler.addBuddy()"/>
+          <toolbarbutton id="button-join-chat"
+                         class="toolbarbutton-1"
+                         label="&joinChatButton.label;"
+                         oncommand="chatHandler.joinChat()"/>
+          <toolbaritem id="chat-status-selector"
+                       orient="horizontal"
+                       class="toolbarbutton-1"
+                       align="center" flex="1">
+            <button type="menu" id="statusTypeIcon" status="available">
+              <menupopup id="setStatusTypeMenupopup"
+                         oncommand="statusSelector.editStatus(event);">
+                <menuitem id="statusTypeAvailable" label="&status.available;"
+                          status="available" class="menuitem-iconic"/>
+                <menuitem id="statusTypeUnavailable" label="&status.unavailable;"
+                          status="unavailable" class="menuitem-iconic"/>
+                <menuseparator id="statusTypeOfflineSeparator"/>
+                <menuitem id="statusTypeOffline" label="&status.offline;"
+                          status="offline" class="menuitem-iconic"/>
+              </menupopup>
+            </button>
+            <label id="statusMessage" crop="end" flex="1" value=""
+                   onclick="statusSelector.statusMessageClick();"/>
+          </toolbaritem>
+          <toolbarbutton id="button-chat-accounts"
+                         class="toolbarbutton-1"
+                         label="&chatAccountsButton.label;"
+                         oncommand="openIMAccountMgr()"/>
+
+          <toolbaritem id="gloda-im-search" insertafter="button-stop"
+                       title="&amp;glodaSearch.title;"
+                       align="center"
+                       flex="1"
+                       class="chromeclass-toolbar-additional">
+            <textbox id="IMSearchInput" 
+                     flex="1"
+                     type="glodacomplete"
+                     searchbutton="true"
+                     enablehistory="false"
+                     timeout="200"
+                     maxlength="192"
+                     placeholder=""
+                     emptytextbase="&searchAllChatMessages.label.base;"
+                     keyLabelNonMac="&search.keyLabel.nonmac;"
+                     keyLabelMac="&search.keyLabel.mac;"
+                     >
+              <!-- Mimic the search/clear buttons of the standard search textbox,
+                   but adjust for the reality that clear doesn't make much sense
+                   since gloda results only show in a tab and the idiom for closing
+                   tabs is closing the tab.  Our binding does process escape to
+                   clear the box, if people want to clear it that way...
+                -->
+              <hbox>
+                <image class="gloda-search-icon"
+                       onclick="document.getElementById('IMSearchInput').doSearch();"
+                       />
+              </hbox>
+            </textbox>
+          </toolbaritem>
+        </toolbarpalette>
+        <toolbarset id="customChatToolbars" context="chat-toolbar-context-menu"/>
+      </toolbox>
+
+      <notificationbox id="chatTabPanel" flex="1">
+        <hbox flex="1">
+          <vbox id="listPaneBox" minwidth="125" width="200" persist="width">
+            <richlistbox id="contactlistbox"
+                         context="buddyListContextMenu"
+                         tooltip="buddyTooltip" flex="1">
+              <imgroup id="conversationsGroup" name="&conversationsHeader.label;"/>
+              <imconv id="searchResultConv" displayname="&searchResultConversation.label;" hidden="true"/>
+              <imgroup id="onlinecontactsGroup" name="&onlineContactsHeader.label;"/>
+              <imgroup id="offlinecontactsGroup" name="&offlineContactsHeader.label;" closed="true"/>
+            </richlistbox>
+          </vbox>
+          <splitter id="listSplitter" collapse="before"/>
+          <deck id="conversationsDeck" flex="1">
+            <vbox flex="1" id="noConvScreen" class="im-placeholder-screen" align="center" pack="center">
+              <hbox id="noConvBox" class="im-placeholder-box" align="top">
+                <image id="noConvImage" class="im-placeholder-image"/>
+                <vbox id="noConvInnerBox" class="im-placeholder-innerbox" flex="1">
+                  <label id="noConvTitle" class="im-placeholder-title" value="&chat.noConv.title;"/>
+                  <description id="noConvDesc" class="im-placeholder-desc">&chat.noConv.description;</description>
+                </vbox>
+                <vbox id="noAccountInnerBox" hidden="true">
+                  <label id="noAccountTitle" class="im-placeholder-title" value="&chat.noAccount.title;"/>
+                  <description id="noAccountDesc" class="im-placeholder-desc">&chat.noAccount.description;</description>
+                  <hbox flex="1">
+                    <spacer flex="1"/>
+                    <button id="openIMAccountWizardButton" label="&chat.accountWizard.button;"
+                            oncommand="openIMAccountWizard();"/>
+                  </hbox>
+                </vbox>
+                <vbox id="noConnectedAccountInnerBox" hidden="true">
+                  <label id="noConnectedAccountTitle" class="im-placeholder-title" value="&chat.noConnectedAccount.title;"/>
+                  <description id="noConnectedAccountDesc" class="im-placeholder-desc">&chat.noConnectedAccount.description;</description>
+                  <hbox flex="1">
+                    <spacer flex="1"/>
+                    <button id="openIMAccountManagerButton" label="&chat.showAccountManager.button;"
+                            oncommand="openIMAccountMgr();"/>
+                  </hbox>
+                </vbox>
+              </hbox>
+            </vbox>
+            <vbox id="logDisplay" flex="1">
+              <deck id="logDisplayDeck" flex="1">
+                <vbox flex="1" id="noPreviousConvScreen" class="im-placeholder-screen" align="center" pack="center">
+                  <hbox id="noPreviousConvBox" class="im-placeholder-box" align="top">
+                    <vbox id="noPreviousConvInnerBox" class="im-placeholder-innerbox" flex="1">
+                      <description id="noPreviousConvDesc" class="im-placeholder-desc">&chat.noPreviousConv.description;</description>
+                    </vbox>
+                  </hbox>
+                </vbox>
+                <vbox flex="1" id="logDisplayBrowserBox">
+                  <browser id="conv-log-browser" type="content-conversation" contextmenu="chatConversationContextMenu" flex="1"/>
+                  <progressmeter id="log-browserProgress" hidden="true"/>
+                  <findbar id="log-findbar" browserid="conv-log-browser"/>
+                </vbox>
+              </deck>
+              <button id="goToConversation" hidden="true"
+                      oncommand="chatHandler.showCurrentConversation();"/>
+            </vbox>
+          </deck>
+          <splitter id="contextSplitter" hidden="true" collapse="after"/>
+          <vbox id="contextPane" hidden="true" width="250" persist="width">
+            <toolbar class="conv-top-info" id="conv-top-info"/>
+            <vbox flex="1" class="conv-chat" width="150">
+              <hbox align="baseline" class="conv-nicklist-header">
+                <label class="conv-nicklist-header-label"
+                       id="participantLabel" control="participantCount"
+                       value="&chat.participants;"/>
+                <textbox flex="1" readonly="true" class="plain" id="participantCount"/>
+              </hbox>
+              <listbox id="nicklist" class="conv-nicklist"
+                       flex="1" seltype="multiple"
+                       tooltip="buddyTooltip"
+                       onclick="onNickClick(event);"
+                       onkeypress="onNicklistKeyPress(event);"/><!-- FIXME event handler -->
+            </vbox>
+            <splitter id="logsSplitter" class="conv-chat"/>
+            <vbox flex="1" id="previousConversations">
+              <label class="conv-logs-header-label"
+                     id="participantLabel"
+                     value="&chat.previousConversations;"/>
+              <listbox flex="1" id="logList" onselect="chatHandler.onLogSelect();"/>
+            </vbox>
+          </vbox>
+        </hbox>
+      </notificationbox>
+    </vbox>
+  </tabpanels>
+</overlay>
new file mode 100644
--- /dev/null
+++ b/mail/components/im/content/chat.css
@@ -0,0 +1,104 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Thunderbird.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@queze.net>.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+imconversation {
+  -moz-binding: url("chrome://messenger/content/chat/imconversation.xml#conversation");
+}
+
+imgroup {
+  -moz-binding: url("chrome://messenger/content/chat/imgroup.xml#group");
+  -moz-box-align: center;
+}
+
+imcontact {
+  -moz-binding: url("chrome://messenger/content/chat/imcontact.xml#contact");
+  -moz-box-align: center;
+}
+
+.startChatBubble, .closeConversationButton {
+  cursor: pointer;
+  -moz-user-focus: ignore;
+  display: none;
+}
+
+imcontact[cansend]:hover .startChatBubble,
+imconv:hover .closeConversationButton {
+  display: -moz-box;
+}
+
+imconv {
+  -moz-binding: url("chrome://messenger/content/chat/imconv.xml#conv");
+  -moz-box-align: center;
+}
+
+.convUnreadCount[value="0"],
+.convUnreadTargetedCount[value="0"] {
+  display: none;
+}
+
+tooltip[type="buddy"] {
+  -moz-binding: url("chrome://messenger/content/chat/imbuddytooltip.xml#tooltip");
+}
+
+browser[type="content-conversation"] {
+  -moz-binding: url("chrome://chat/content/convbrowser.xml#browser");
+}
+
+#contextPane:not([chat]) > .conv-chat {
+  display: none;
+}
+
+.conv-top-info {
+  -moz-binding: url("chrome://messenger/content/chat/imconversation.xml#conv-info-large") !important;
+}
+
+#IMSearchInput {
+  -moz-binding: url("chrome://messenger/content/chat/imsearch.xml#IMGlodaSearch");
+}
+
+
+#statusTypeIcon {
+  cursor: pointer;
+}
+
+#statusMessage:not([statusType="offline"]) {
+  cursor: text;
+}
+
+#statusMessage[editing] {
+  -moz-appearance: textfield;
+  -moz-binding: url('chrome://global/content/bindings/textbox.xml#textbox');
+}
new file mode 100644
--- /dev/null
+++ b/mail/components/im/content/imAccount.xml
@@ -0,0 +1,366 @@
+<?xml version="1.0"?>
+<!-- ***** BEGIN LICENSE BLOCK *****
+   - Version: MPL 1.1/GPL 2.0/LGPL 2.1
+   -
+   - The contents of this file are subject to the Mozilla Public License Version
+   - 1.1 (the "License"); you may not use this file except in compliance with
+   - the License. You may obtain a copy of the License at
+   - http://www.mozilla.org/MPL/
+   -
+   - Software distributed under the License is distributed on an "AS IS" basis,
+   - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+   - for the specific language governing rights and limitations under the
+   - License.
+   -
+   - The Original Code is the Instantbird messenging client, released
+   - 2007.
+   -
+   - The Initial Developer of the Original Code is
+   - Florian QUEZE <florian@instantbird.org>.
+   - Portions created by the Initial Developer are Copyright (C) 2007
+   - the Initial Developer. All Rights Reserved.
+   -
+   - Contributor(s):
+   -   Romain Bezut <romain@bezut.info>
+   -
+   - Alternatively, the contents of this file may be used under the terms of
+   - either the GNU General Public License Version 2 or later (the "GPL"), or
+   - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+   - in which case the provisions of the GPL or the LGPL are applicable instead
+   - of those above. If you wish to allow use of your version of this file only
+   - under the terms of either the GPL or the LGPL, and not to allow others to
+   - use your version of this file under the terms of the MPL, indicate your
+   - decision by deleting the provisions above and replace them with the notice
+   - and other provisions required by the GPL or the LGPL. If you do not delete
+   - the provisions above, a recipient may use your version of this file under
+   - the terms of any one of the MPL, the GPL or the LGPL.
+   -
+   - ***** END LICENSE BLOCK ***** -->
+
+
+<!DOCTYPE bindings [
+  <!ENTITY % accountsDTD SYSTEM "chrome://messenger/locale/imAccounts.dtd">
+  %accountsDTD;
+]>
+
+<bindings id="accountBindings"
+          xmlns="http://www.mozilla.org/xbl"
+          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+          xmlns:xbl="http://www.mozilla.org/xbl">
+
+  <binding id="account" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+    <content>
+      <xul:vbox flex="1">
+        <xul:hbox flex="1" align="top">
+          <xul:vbox>
+            <xul:stack xbl:inherits="tooltiptext=protocol">
+              <xul:image xbl:inherits="src=prplicon" class="accountIcon"/>
+              <xul:image class="accountStateIcon"/>
+            </xul:stack>
+            <xul:spacer flex="1"/>
+          </xul:vbox>
+          <xul:vbox flex="1">
+           <xul:label xbl:inherits="value=name" crop="end" class="accountName"/>
+           <xul:label class="connecting" crop="end" anonid="connecting" value="&account.connecting;"/>
+           <xul:label class="connected" crop="end" anonid="connected"/>
+           <xul:label class="disconnecting" crop="end" value="&account.disconnecting;"/>
+           <xul:label class="disconnected" crop="end" value="&account.disconnected;"/>
+           <xul:description class="error" anonid="error"/>
+           <xul:description class="error" anonid="reconnect"/>
+           <xul:spacer flex="1"/>
+          </xul:vbox>
+          <xul:checkbox label="&account.autoSignOn.label;" dir="reverse"
+                        xbl:inherits="checked=autologin" class="autoSignOn"
+                        accesskey="&account.autoSignOn.accesskey;"
+                        oncommand="gAccountManager.autologin()"/>
+        </xul:hbox>
+        <xul:hbox flex="1" class="account-buttons" anonid="buttons"
+                  xbl:inherits="autologin"/>
+      </xul:vbox>
+    </content>
+    <implementation>
+     <method name="build">
+      <parameter name="aAccount"/>
+      <body>
+      <![CDATA[
+        this._account = aAccount;
+        this.setAttribute("name", aAccount.name);
+        this.setAttribute("id", aAccount.id);
+        var proto = aAccount.protocol;
+        this.setAttribute("protocol", proto.name);
+        this.setAttribute("prplicon", proto.iconBaseURI + "icon32.png");
+        var state = "Unknown";
+        if (this._account.connected) {
+          state = "connected";
+          this.refreshConnectedLabel();
+        } else if (this._account.disconnected) {
+          state = "disconnected";
+          if (this._account.connectionErrorReason != Ci.prplIAccount.NO_ERROR)
+            this.updateConnectionError();
+          else
+            this.removeAttribute("error");
+        } else if (this._account.connecting) {
+          state = "connecting";
+          this.updateConnectionState();
+        } else if (this._account.disconnecting) {
+          state = "connected";
+        }
+        this.setAttribute("state", state);
+        this.autoLogin = aAccount.autoLogin;
+      ]]>
+      </body>
+     </method>
+
+     <method name="updateConnectionState">
+      <body>
+      <![CDATA[
+        var bundle = document.getElementById("accountsBundle");
+        const key = "account.connection.progress";
+        var text = this._account.connectionStateMsg;
+        text = text ? bundle.getFormattedString(key, [text])
+                    : bundle.getString("account.connecting");
+
+        var progress = document.getAnonymousElementByAttribute(this, "anonid",
+                                                               "connecting");
+        progress.setAttribute("value", text);
+        if (this.reconnectUpdateInterval)
+          this._cancelReconnectTimer();
+      ]]>
+      </body>
+     </method>
+
+     <method name="updateConnectionError">
+      <body>
+      <![CDATA[
+        var bundle = document.getElementById("accountsBundle");
+        const key = "account.connection.error";
+        var account = this._account;
+        var text;
+        let errorReason = account.connectionErrorReason;
+        if (errorReason == Ci.imIAccount.ERROR_UNKNOWN_PRPL)
+          text = bundle.getFormattedString(key + "UnknownPrpl",
+                                           [account.protocol.id]);
+        else if (errorReason == Ci.imIAccount.ERROR_MISSING_PASSWORD)
+          text = bundle.getString(key + "EnteringPasswordRequired");
+        else if (errorReason == Ci.imIAccount.ERROR_CRASHED)
+          text = bundle.getString(key + "CrashedAccount");
+        else
+          text = account.connectionErrorMessage;
+
+        if (errorReason != Ci.imIAccount.ERROR_MISSING_PASSWORD)
+          text = bundle.getFormattedString(key, [text]);
+
+        this.setAttribute("error", "true");
+        var error = document.getAnonymousElementByAttribute(this, "anonid",
+                                                            "error");
+        error.textContent = text;
+
+        var updateReconnect = (function() {
+          var date = Math.round((account.timeOfNextReconnect - Date.now()) / 1000);
+          let reconnect = "";
+          if (date > 0) {
+            let [val1, unit1, val2, unit2] = DownloadUtils.convertTimeUnits(date);
+            if (!val2)
+              reconnect = bundle.getFormattedString("account.reconnectInSingle",
+                                                    [val1, unit1])
+            else
+              reconnect = bundle.getFormattedString("account.reconnectInDouble",
+                                                    [val1, unit1, val2, unit2])
+          }
+          document.getAnonymousElementByAttribute(this, "anonid", "reconnect")
+                  .textContent = reconnect;
+          return reconnect;
+        }).bind(this);
+        if (updateReconnect() && !this.reconnectUpdateInterval) {
+          this.setAttribute("reconnectPending", "true");
+          this.reconnectUpdateInterval = setInterval(updateReconnect, 1000);
+          gAccountManager.disableCommandItems();
+        }
+      ]]>
+      </body>
+     </method>
+
+     <method name="refreshConnectedLabel">
+       <body>
+       <![CDATA[
+         var bundle = document.getElementById("accountsBundle");
+         var date =
+           60 * Math.floor((Date.now() - this._account.timeOfLastConnect) / 60000);
+         let value;
+         if (date > 0) {
+           let [val1, unit1, val2, unit2] = DownloadUtils.convertTimeUnits(date);
+           if (!val2)
+             value = bundle.getFormattedString("account.connectedForSingle",
+                                               [val1, unit1])
+           else
+             value = bundle.getFormattedString("account.connectedForDouble",
+                                               [val1, unit1, val2, unit2])
+         }
+         else
+           value = bundle.getString("account.connectedForSeconds");
+         this.connectedLabel.value = value;
+       ]]>
+       </body>
+     </method>
+
+     <method name="_cancelReconnectTimer">
+       <body>
+       <![CDATA[
+         this.removeAttribute("reconnectPending");
+         clearInterval(this.reconnectUpdateInterval);
+         delete this.reconnectUpdateInterval;
+         gAccountManager.disableCommandItems();
+       ]]>
+       </body>
+     </method>
+
+     <method name="cancelReconnection">
+       <body>
+       <![CDATA[
+         if (this.reconnectUpdateInterval) {
+           this._cancelReconnectTimer();
+           this._account.cancelReconnection();
+         }
+       ]]>
+       </body>
+     </method>
+
+     <method name="restoreItems">
+       <body>
+       <![CDATA[
+         // Called after a removal and reinsertion of the binding
+         this._buttons = null;
+         this._connectedLabel = null;
+         if (this._account.connected)
+           this.refreshConnectedLabel();
+         if (this._account.connectionErrorReason == Ci.prplIAccount.NO_ERROR)
+           this.updateConnectionState();
+         else
+           this.updateConnectionError();
+       ]]>
+       </body>
+     </method>
+
+     <method name="destroy">
+       <body>
+       <![CDATA[
+         // If we have a reconnect timer, stop it:
+         // it will throw errors otherwise (see bug 480).
+         // clearInterval is harmless if the timer doesn't exist.
+         clearInterval(this.reconnectUpdateInterval);
+         delete this.reconnectUpdateInterval;
+       ]]>
+       </body>
+     </method>
+
+     <property name="autoLogin">
+       <getter>
+         <![CDATA[
+           return this.hasAttribute("autologin");
+         ]]>
+       </getter>
+       <setter>
+         <![CDATA[
+           if (val)
+             this.setAttribute("autologin", "true");
+           else
+             this.removeAttribute("autologin");
+           if (this._account.autoLogin != val)
+             this._account.autoLogin = val;
+           return val;
+         ]]>
+       </setter>
+     </property>
+
+     <!-- override the default accessible name -->
+     <property name="label" onget="return this.getAttribute('name');"/>
+
+     <property name="account" onget="return this._account;"/>
+
+     <property name="connectedLabel">
+       <getter>
+         <![CDATA[
+           if (!this._connectedLabel)
+             this._connectedLabel =
+               document.getAnonymousElementByAttribute(this, "anonid", "connected");
+           return this._connectedLabel;
+         ]]>
+       </getter>
+     </property>
+
+     <property name="buttons">
+       <getter>
+         <![CDATA[
+           if (!this._buttons)
+             this._buttons =
+               document.getAnonymousElementByAttribute(this, "anonid", "buttons");
+           return this._buttons;
+         ]]>
+       </getter>
+     </property>
+
+    </implementation>
+    <handlers>
+      <handler event="dblclick">
+        <![CDATA[
+          if (event.button == 0) {
+            // If we double clicked on a widget that has already done
+            // something with the first click, we should ignore the event
+            var localName = event.originalTarget.localName;
+            if (localName != "button" && localName != "checkbox")
+              this.buttons.proceedDefaultAction();
+          }
+
+          // Prevent from loading an account wizzard
+          event.stopPropagation();
+        ]]>
+      </handler>
+    </handlers>
+  </binding>
+
+  <binding id="buttons" extends="xul:hbox">
+    <content>
+      <xul:button class="disconnectButton"
+                  command="cmd_disconnect"
+                  anonid="disconnect"/>
+      <xul:button class="connectButton"
+                  command="cmd_connect"
+                  anonid="connect"/>
+      <xul:spacer flex="1"/>
+      <xul:button command="cmd_edit"/>
+    </content>
+    <implementation>
+     <property name="activeButton" readonly="true">
+       <getter>
+       <![CDATA[
+         let action =
+           document.getBindingParent(this).account.disconnected ? "connect"
+                                                                : "disconnect";
+         return document.getAnonymousElementByAttribute(this, "anonid", action);
+       ]]>
+       </getter>
+     </property>
+
+     <method name="setFocus">
+      <body>
+      <![CDATA[
+        let focusTarget = this.activeButton;
+        if (focusTarget.disabled)
+          focusTarget = document.getElementById("accountlist");
+        focusTarget.focus();
+      ]]>
+      </body>
+     </method>
+
+     <method name="proceedDefaultAction">
+      <body>
+      <![CDATA[
+        this.activeButton.click();
+      ]]>
+      </body>
+     </method>
+    </implementation>
+  </binding>
+
+  <binding id="nobuttons" extends="xul:hbox"/>
+</bindings>
new file mode 100644
--- /dev/null
+++ b/mail/components/im/content/imAccountWizard.js
@@ -0,0 +1,528 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2007.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Benedikt P.
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+Cu.import("resource:///modules/imServices.jsm");
+
+const PREF_EXTENSIONS_GETMOREPROTOCOLSURL = "extensions.getMoreProtocolsURL";
+
+var accountWizard = {
+  onload: function aw_onload() {
+    // Ensure the im core is initialized before we get a list of protocols.
+    Services.core.init();
+
+    accountWizard.setGetMoreProtocols();
+
+    var protoList = document.getElementById("protolist");
+    var protos = [];
+    for (let proto in this.getProtocols())
+      protos.push(proto);
+    protos.sort(function(a, b) a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
+    protos.forEach(function(proto) {
+      var id = proto.id;
+      var item = protoList.appendItem(proto.name, id, id);
+      item.setAttribute("image", proto.iconBaseURI + "icon.png");
+      item.setAttribute("class", "listitem-iconic");
+    });
+
+    // there is a strange selection bug without this timeout
+    setTimeout(function() {
+      protoList.selectedIndex = 0;
+    }, 0);
+
+    Services.obs.addObserver(this, "prpl-quit", false);
+    window.addEventListener("unload", this.unload);
+  },
+  unload: function aw_unload() {
+    Services.obs.removeObserver(accountWizard, "prpl-quit");
+  },
+  observe: function am_observe(aObject, aTopic, aData) {
+    if (aTopic == "prpl-quit") {
+      // libpurple is being uninitialized. We can't create any new
+      // account so keeping this wizard open would be pointless, close it.
+      window.close();
+    }
+  },
+
+  getUsername: function aw_getUsername() {
+    // If the first username textbox is empty, make sure we return an empty
+    // string so that it blocks the 'next' button of the wizard.
+    if (!this.userNameBoxes[0].value)
+      return "";
+
+    return this.userNameBoxes.reduce(function(prev, elt) prev + elt.value, "");
+  },
+
+  checkUsername: function aw_checkUsername() {
+    var wizard = document.getElementById("accountWizard");
+    var name = accountWizard.getUsername();
+    var duplicateWarning = document.getElementById("duplicateAccount");
+    if (!name) {
+      wizard.canAdvance = false;
+      duplicateWarning.hidden = true;
+      return;
+    }
+
+    var exists = accountWizard.proto.accountExists(name);
+    wizard.canAdvance = !exists;
+    duplicateWarning.hidden = !exists;
+  },
+
+  selectProtocol: function aw_selectProtocol() {
+    var protoList = document.getElementById("protolist");
+    var id = protoList.selectedItem.value;
+    this.proto = Services.core.getProtocolById(id);
+
+    return true;
+  },
+
+
+  insertUsernameField: function aw_insertUsernameField(aName, aLabel, aParent,
+                                                       aDefaultValue) {
+    var hbox = document.createElement("hbox");
+    hbox.setAttribute("id", aName + "-hbox");
+    hbox.setAttribute("align", "baseline");
+    hbox.setAttribute("equalsize", "always");
+
+    var label = document.createElement("label");
+    label.setAttribute("value", aLabel);
+    label.setAttribute("control", aName);
+    label.setAttribute("id", aName + "-label");
+    hbox.appendChild(label);
+
+    var textbox = document.createElement("textbox");
+    textbox.setAttribute("id", aName);
+    textbox.setAttribute("flex", 1);
+    if (aDefaultValue)
+      textbox.setAttribute("value", aDefaultValue);
+    textbox.addEventListener("input", accountWizard.checkUsername);
+    hbox.appendChild(textbox);
+
+    aParent.appendChild(hbox);
+    return textbox;
+  },
+
+  showUsernamePage: function aw_showUsernamePage() {
+    var proto = this.proto.id;
+    if ("userNameBoxes" in this && this.userNameProto == proto) {
+      this.checkUsername();
+      return;
+    }
+
+    var bundle = document.getElementById("accountsBundle");
+    var usernameInfo;
+    var emptyText = this.proto.usernameEmptyText;
+    if (emptyText) {
+      usernameInfo =
+        bundle.getFormattedString("accountUsernameInfoWithDescription",
+                                  [emptyText, this.proto.name]);
+    }
+    else {
+      usernameInfo =
+        bundle.getFormattedString("accountUsernameInfo", [this.proto.name]);
+    }
+    document.getElementById("usernameInfo").textContent = usernameInfo;
+
+    var vbox = document.getElementById("userNameBox");
+    // remove anything that may be there for another protocol
+    var child;
+    while ((child = vbox.firstChild))
+      vbox.removeChild(child);
+
+    var splits = [];
+    for (let split in this.getProtoUserSplits())
+      splits.push(split);
+
+    var label = bundle.getString("accountUsername");
+    this.userNameBoxes = [this.insertUsernameField("name", label, vbox)];
+    this.userNameBoxes[0].emptyText = emptyText;
+
+    for (let i = 0; i < splits.length; ++i) {
+      this.userNameBoxes.push({value: splits[i].separator});
+      label = bundle.getFormattedString("accountColon", [splits[i].label]);
+      let defaultVal = splits[i].defaultValue;
+      this.userNameBoxes.push(this.insertUsernameField("username-split-" + i,
+                                                       label, vbox,
+                                                       defaultVal));
+    }
+    this.userNameBoxes[0].focus();
+    this.userNameProto = proto;
+    this.checkUsername();
+  },
+
+  hideUsernamePage: function aw_hideUsernamePage() {
+    document.getElementById("accountWizard").canAdvance = true;
+    var next = "account" +
+      (this.proto.noPassword ? "advanced" : "password");
+    document.getElementById("accountusername").next = next;
+  },
+
+  showAdvanced: function aw_showAdvanced() {
+    // ensure we don't destroy user data if it's not necessary
+    var id = this.proto.id;
+    if ("protoSpecOptId" in this && this.protoSpecOptId == id)
+      return;
+    this.protoSpecOptId = id;
+
+    this.populateProtoSpecificBox();
+
+    let alias = document.getElementById("alias");
+    alias.focus();
+  },
+
+  createTextbox: function aw_createTextbox(aType, aValue, aLabel, aName) {
+    var box = document.createElement("hbox");
+    box.setAttribute("align", "baseline");
+    box.setAttribute("equalsize", "always");
+
+    var label = document.createElement("label");
+    label.setAttribute("value", aLabel);
+    label.setAttribute("control", aName);
+    box.appendChild(label);
+
+    var textbox = document.createElement("textbox");
+    if (aType)
+      textbox.setAttribute("type", aType);
+    textbox.setAttribute("value", aValue);
+    textbox.setAttribute("id", aName);
+    textbox.setAttribute("flex", "1");
+
+    box.appendChild(textbox);
+    return box;
+  },
+
+  createMenulist: function aw_createMenulist(aList, aLabel, aName) {
+    var box = document.createElement("hbox");
+    box.setAttribute("align", "baseline");
+
+    var label = document.createElement("label");
+    label.setAttribute("value", aLabel);
+    label.setAttribute("control", aName);
+    box.appendChild(label);
+
+    aList.QueryInterface(Ci.nsISimpleEnumerator);
+    var menulist = document.createElement("menulist");
+    menulist.setAttribute("id", aName);
+    var popup = menulist.appendChild(document.createElement("menupopup"));
+    while (aList.hasMoreElements()) {
+      let elt = aList.getNext();
+      let item = document.createElement("menuitem");
+      item.setAttribute("label", elt.name);
+      item.setAttribute("value", elt.value);
+      popup.appendChild(item);
+    }
+    box.appendChild(menulist);
+    return box;
+  },
+
+  populateProtoSpecificBox: function aw_populate() {
+    var id = this.proto.id;
+    var box = document.getElementById("protoSpecific");
+    var child;
+    while ((child = box.firstChild))
+      box.removeChild(child);
+    var visible = false;
+    for (let opt in this.getProtoOptions()) {
+      var text = opt.label;
+      var name = id + "-" + opt.name;
+      switch (opt.type) {
+      case opt.typeBool:
+        var chk = document.createElement("checkbox");
+        chk.setAttribute("label", text);
+        chk.setAttribute("id", name);
+        if (opt.getBool())
+          chk.setAttribute("checked", "true");
+        box.appendChild(chk);
+        break;
+      case opt.typeInt:
+        box.appendChild(this.createTextbox("number", opt.getInt(),
+                                           text, name));
+        break;
+      case opt.typeString:
+        box.appendChild(this.createTextbox(null, opt.getString(), text, name));
+        break;
+      case opt.typeList:
+        box.appendChild(this.createMenulist(opt.getList(), text, name));
+        document.getElementById(name).value = opt.getListDefault();
+        break;
+      default:
+        throw "unknown preference type " + opt.type;
+      }
+      visible = true;
+    }
+    document.getElementById("protoSpecificGroupbox").hidden = !visible;
+    if (visible) {
+      var bundle = document.getElementById("accountsBundle");
+      document.getElementById("protoSpecificCaption").label =
+        bundle.getFormattedString("protoOptions", [this.proto.name]);
+    }
+  },
+
+  createSummaryRow: function aw_createSummaryRow(aLabel, aValue) {
+    var row = document.createElement("row");
+    row.setAttribute("align", "baseline");
+
+    var label = document.createElement("label");
+    label.setAttribute("class", "header");
+    if (aLabel.length > 20) {
+      aLabel = aLabel.substring(0, 20);
+      aLabel += "…";
+    }
+    label.setAttribute("value", aLabel);
+    row.appendChild(label);
+
+    var textbox = document.createElement("textbox");
+    textbox.setAttribute("value", aValue);
+    textbox.setAttribute("class", "plain");
+    textbox.setAttribute("readonly", true);
+    row.appendChild(textbox);
+
+    return row;
+  },
+
+  showSummary: function aw_showSummary() {
+    var rows = document.getElementById("summaryRows");
+    var bundle = document.getElementById("accountsBundle");
+    var child;
+    while ((child = rows.firstChild))
+      rows.removeChild(child);
+
+    var label = document.getElementById("protoLabel").value;
+    rows.appendChild(this.createSummaryRow(label, this.proto.name));
+    this.username = this.getUsername();
+    label = bundle.getString("accountUsername");
+    rows.appendChild(this.createSummaryRow(label, this.username));
+    if (!this.proto.noPassword) {
+      this.password = this.getValue("password");
+      if (this.password) {
+        label = document.getElementById("passwordLabel").value;
+        var pass = "";
+        for (let i = 0; i < this.password.length; ++i)
+          pass += "*";
+        rows.appendChild(this.createSummaryRow(label, pass));
+      }
+    }
+    this.alias = this.getValue("alias");
+    if (this.alias) {
+      label = document.getElementById("aliasLabel").value;
+      rows.appendChild(this.createSummaryRow(label, this.alias));
+    }
+
+/* FIXME
+    if (this.proto.newMailNotification)
+      rows.appendChild(this.createSummaryRow("Notify of new mails:",
+                                             this.getValue("newMailNotification")));
+*/
+
+    var id = this.proto.id;
+    this.prefs = [ ];
+    for (let opt in this.getProtoOptions()) {
+      let name = opt.name;
+      let eltName = id + "-" + name;
+      let val = this.getValue(eltName);
+      // The value will be undefined if the proto specific groupbox has never been opened
+      if (val === undefined)
+        continue;
+      switch (opt.type) {
+      case opt.typeBool:
+        if (val != opt.getBool())
+          this.prefs.push({opt: opt, name: name, value: !!val});
+        break;
+      case opt.typeInt:
+        if (val != opt.getInt())
+          this.prefs.push({opt: opt, name: name, value: val});
+        break;
+      case opt.typeString:
+        if (val != opt.getString())
+          this.prefs.push({opt: opt, name: name, value: val});
+        break;
+      case opt.typeList:
+        if (val != opt.getListDefault())
+          this.prefs.push({opt: opt, name: name, value: val});
+        break;
+      default:
+        throw "unknown preference type " + opt.type;
+      }
+    }
+
+    for (let i = 0; i < this.prefs.length; ++i) {
+      let opt = this.prefs[i];
+      let label = bundle.getFormattedString("accountColon", [opt.opt.label]);
+      rows.appendChild(this.createSummaryRow(label, opt.value));
+    }
+  },
+
+  createAccount: function aw_createAccount() {
+    var acc = Services.accounts.createAccount(this.username, this.proto.id);
+    if (!this.proto.noPassword && this.password)
+      acc.password = this.password;
+    if (this.alias)
+      acc.alias = this.alias;
+
+    for (let i = 0; i < this.prefs.length; ++i) {
+      let option = this.prefs[i];
+      let opt = option.opt;
+      switch(opt.type) {
+      case opt.typeBool:
+        acc.setBool(option.name, option.value);
+        break;
+      case opt.typeInt:
+        acc.setInt(option.name, option.value);
+        break;
+      case opt.typeString:
+      case opt.typeList:
+        acc.setString(option.name, option.value);
+        break;
+      default:
+        throw "unknown type";
+      }
+    }
+    var autologin = this.getValue("connectNow");
+    acc.autoLogin = autologin;
+
+    acc.save();
+
+    try {
+      if (autologin)
+        acc.connect();
+    } catch (e) {
+      // If the connection fails (for example if we are currently in
+      // offline mode), we still want to close the account wizard
+    }
+
+    if (window.opener) {
+      var am = window.opener.gAccountManager;
+      if (am)
+        am.selectAccount(acc.id);
+    }
+
+    var accountManager = Cc["@mozilla.org/messenger/account-manager;1"]
+                           .getService(Ci.nsIMsgAccountManager);
+    
+    var inServer =
+      accountManager.createIncomingServer(this.username,
+                                          this.proto.id, // hostname
+                                          "im");
+    inServer.wrappedJSObject.imAccount = acc;
+
+    var account = accountManager.createAccount();
+    // Avoid new folder notifications.
+    inServer.valid = false;
+    account.incomingServer = inServer;
+    inServer.valid = true;
+    accountManager.notifyServerLoaded(inServer);
+
+    return true;
+  },
+
+  getValue: function aw_getValue(aId) {
+    var elt = document.getElementById(aId);
+    if ("checked" in elt)
+      return elt.checked;
+    if ("value" in elt)
+      return elt.value;
+    // If the groupbox has never been opened, the binding isn't attached
+    // so the attributes don't exist. The calling code in showSummary
+    // has a special handling of the undefined value for this case.
+    return undefined;
+  },
+
+  getIter: function(aEnumerator) {
+    while (aEnumerator.hasMoreElements())
+      yield aEnumerator.getNext();
+  },
+  getProtocols: function aw_getProtocols()
+    this.getIter(Services.core.getProtocols()),
+  getProtoOptions: function aw_getProtoOptions()
+    this.getIter(this.proto.getOptions()),
+  getProtoUserSplits: function aw_getProtoUserSplits()
+    this.getIter(this.proto.getUsernameSplit()),
+
+  onGroupboxKeypress: function aw_onGroupboxKeypress(aEvent) {
+    var target = aEvent.target;
+    var code = aEvent.charCode || aEvent.keyCode;
+    if (code == KeyEvent.DOM_VK_SPACE ||
+        (code == KeyEvent.DOM_VK_LEFT && !target.hasAttribute("closed")) ||
+        (code == KeyEvent.DOM_VK_RIGHT && target.hasAttribute("closed")))
+        this.toggleGroupbox(target.id);
+  },
+
+  toggleGroupbox: function aw_toggleGroupbox(id) {
+    var elt = document.getElementById(id);
+    if (elt.hasAttribute("closed")) {
+      elt.removeAttribute("closed");
+      if (elt.flexWhenOpened)
+        elt.flex = elt.flexWhenOpened;
+    }
+    else {
+      elt.setAttribute("closed", "true");
+      if (elt.flex) {
+        elt.flexWhenOpened = elt.flex;
+        elt.flex = 0;
+      }
+    }
+  },
+
+  /* Check for correctness and set URL for the "Get more protocols..."-link
+   *  Stripped down code from preferences/themes.js
+   */
+  setGetMoreProtocols: function (){
+    let prefURL = PREF_EXTENSIONS_GETMOREPROTOCOLSURL;
+    var getMore = document.getElementById("getMoreProtocols");
+    var showGetMore = false;
+    const nsIPrefBranch2 = Components.interfaces.nsIPrefBranch2;
+
+    if (Services.prefs.getPrefType(prefURL) != nsIPrefBranch2.PREF_INVALID) {
+      try {
+        var getMoreURL = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
+                                   .getService(Components.interfaces.nsIURLFormatter)
+                                   .formatURLPref(prefURL);
+        getMore.setAttribute("getMoreURL", getMoreURL);
+        showGetMore = getMoreURL != "about:blank";
+      }
+      catch (e) { }
+    }
+    getMore.hidden = !showGetMore;
+  },
+
+  openURL: function (aURL) {
+    Components.classes["@mozilla.org/uriloader/external-protocol-service;1"]
+              .getService(Components.interfaces.nsIExternalProtocolService)
+              .loadUrl(Services.io.newURI(aURL, null, null));
+  }
+};
new file mode 100644
--- /dev/null
+++ b/mail/components/im/content/imAccountWizard.xul
@@ -0,0 +1,141 @@
+<?xml version="1.0"?>
+<!-- ***** BEGIN LICENSE BLOCK *****
+   - Version: MPL 1.1/GPL 2.0/LGPL 2.1
+   -
+   - The contents of this file are subject to the Mozilla Public License Version
+   - 1.1 (the "License"); you may not use this file except in compliance with
+   - the License. You may obtain a copy of the License at
+   - http://www.mozilla.org/MPL/
+   -
+   - Software distributed under the License is distributed on an "AS IS" basis,
+   - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+   - for the specific language governing rights and limitations under the
+   - License.
+   -
+   - The Original Code is the Instantbird messenging client, released
+   - 2007.
+   -
+   - The Initial Developer of the Original Code is
+   - Florian QUEZE <florian@instantbird.org>.
+   - Portions created by the Initial Developer are Copyright (C) 2007
+   - the Initial Developer. All Rights Reserved.
+   -
+   - Contributor(s):
+   -   Benedikt P.
+   -
+   - Alternatively, the contents of this file may be used under the terms of
+   - either the GNU General Public License Version 2 or later (the "GPL"), or
+   - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+   - in which case the provisions of the GPL or the LGPL are applicable instead
+   - of those above. If you wish to allow use of your version of this file only
+   - under the terms of either the GPL or the LGPL, and not to allow others to
+   - use your version of this file under the terms of the MPL, indicate your
+   - decision by deleting the provisions above and replace them with the notice
+   - and other provisions required by the GPL or the LGPL. If you do not delete
+   - the provisions above, a recipient may use your version of this file under
+   - the terms of any one of the MPL, the GPL or the LGPL.
+   -
+   - ***** END LICENSE BLOCK ***** -->
+
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/imAccountWizard.css" type="text/css"?>
+
+<!DOCTYPE wizard [
+  <!ENTITY % accountWizardDTD SYSTEM "chrome://messenger/locale/imAccountWizard.dtd">
+  <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+  %accountWizardDTD;
+  %brandDTD;
+]>
+
+<wizard id="accountWizard" title="&windowTitle.label;"
+        windowtype="Messenger:accountWizard"
+        onwizardfinish="return accountWizard.createAccount();"
+        onload="accountWizard.onload();"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+  <script type="application/javascript" src="chrome://messenger/content/chat/imAccountWizard.js"/>
+  <stringbundle id="accountsBundle" src="chrome://messenger/locale/imAccounts.properties"/>
+
+  <wizardpage id="accountprotocol" pageid="accountprotocol" next="accountusername"
+              label="&accountProtocolTitle.label;"
+              onpageadvanced="return accountWizard.selectProtocol();">
+    <description>&accountProtocolInfo.label;</description>
+    <separator/>
+    <label value="&accountProtocolField.label;" control="protolist"
+           id="protoLabel" hidden="true"/>
+    <listbox flex="1" id="protolist"
+             ondblclick="document.getElementById('accountWizard').advance();"/>
+    <hbox pack="end">
+      <label id="getMoreProtocols" class="text-link" value="&accountProtocolGetMore.label;"
+             onclick="if (event.button == 0) { accountWizard.openURL(this.getAttribute('getMoreURL')); }"/>
+    </hbox>
+  </wizardpage>
+
+  <wizardpage id="accountusername" pageid="accountusername" next="accountpassword"
+              label="&accountUsernameTitle.label;"
+              onpageshow="accountWizard.showUsernamePage();"
+              onpagehide="accountWizard.hideUsernamePage();">
+    <description id="usernameInfo"/>
+    <separator/>
+    <vbox id="userNameBox"/>
+    <separator/>
+    <description id="duplicateAccount" hidden="true">&accountUsernameDuplicate.label;</description>
+  </wizardpage>
+
+  <wizardpage id="accountpassword" pageid="accountpassword" next="accountadvanced"
+              label="&accountPasswordTitle.label;">
+    <description>&accountPasswordInfo.label;</description>
+    <separator/>
+    <hbox id="passwordBox" align="baseline">
+      <label value="&accountPasswordField.label;" control="password" id="passwordLabel"/>
+      <textbox id="password" type="password"/>
+    </hbox>
+    <separator/>
+    <description id="passwordManagerDescription">&accountPasswordManager.label;</description>
+  </wizardpage>
+
+  <wizardpage id="accountadvanced" pageid="accountadvanced" next="accountsummary"
+              label="&accountAdvancedTitle.label;"
+              onpageshow="accountWizard.showAdvanced();">
+    <description>&accountAdvancedInfo.label;</description>
+    <separator class="thin"/>
+    <groupbox id="aliasGroupbox" class="collapsable"
+              onkeypress="accountWizard.onGroupboxKeypress(event)">
+      <caption id="aliasGroupboxCaption" label="&accountAliasGroupbox.caption;"
+               onclick="accountWizard.toggleGroupbox('aliasGroupbox')"/>
+      <hbox id="aliasBox" align="baseline">
+        <label value="&accountAliasField.label;" control="alias" id="aliasLabel"/>
+        <textbox id="alias"/>
+      </hbox>
+      <description>&accountAliasInfo.label;</description>
+    </groupbox>
+
+    <checkbox id="newMailNotification"
+              label="&accountAdvanced.newMailNotification.label;" hidden="true"/>
+
+    <groupbox id="protoSpecificGroupbox" class="collapsable" closed="true"
+              onkeypress="accountWizard.onGroupboxKeypress(event)">
+      <caption id="protoSpecificCaption"
+               onclick="accountWizard.toggleGroupbox('protoSpecificGroupbox')"/>
+      <vbox id="protoSpecific" flex="1"/>
+    </groupbox>
+  </wizardpage>
+
+  <wizardpage id="accountsummary" pageid="accountsummary"
+              label="&accountSummaryTitle.label;"
+              onpageshow="accountWizard.showSummary();">
+    <description>&accountSummaryInfo.label;</description>
+    <separator/>
+    <grid id="summarygrid" flex="1">
+      <columns>
+        <column/>
+        <column flex="1"/>
+      </columns>
+      <rows id="summaryRows"/>
+    </grid>
+    <separator/>
+    <checkbox id="connectNow" label= "&accountSummary.connectNow.label;" checked="true"/>
+  </wizardpage>
+
+</wizard>
new file mode 100644
--- /dev/null
+++ b/mail/components/im/content/imAccounts.css
@@ -0,0 +1,79 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2007.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+richlistitem {
+  -moz-binding: url("chrome://messenger/content/chat/imAccount.xml#account");
+}
+
+richlistitem .account-buttons {
+  -moz-binding: url("chrome://messenger/content/chat/imAccount.xml#nobuttons");
+}
+richlistitem[selected="true"] .account-buttons {
+  -moz-binding: url("chrome://messenger/content/chat/imAccount.xml#buttons");
+}
+
+richlistitem:not([state="connected"]) .connected,
+richlistitem:not([state="connecting"]) .connecting,
+richlistitem:not([state="disconnected"]) .disconnected,
+richlistitem:not([state="disconnecting"]) .disconnecting,
+richlistitem:not([error="true"]) .error,
+richlistitem:not([error="true"]) .errorIcon,
+richlistitem:not([state="disconnected"]) .error,
+richlistitem[error="true"] .disconnected,
+richlistitem[selected="true"]:not([state="disconnected"]) .connectButton,
+richlistitem[selected="true"][state="disconnected"] .disconnectButton,
+richlistitem[selected="true"][state="disconnecting"] .disconnectButton,
+richlistitem:not([selected="true"]) .autoSignOn,
+richlistitem:not([reconnectPending="true"]) description[anonid="reconnect"]
+{
+  display: none;
+}
+
+#statusTypeIcon {
+  cursor: pointer;
+}
+
+#displayName,
+#statusMessage:not([statusType="offline"]) {
+  cursor: text;
+}
+
+#displayName[editing],
+#statusMessage[editing] {
+  -moz-appearance: textfield;
+  -moz-binding: url('chrome://global/content/bindings/textbox.xml#textbox');
+}
new file mode 100644
--- /dev/null
+++ b/mail/components/im/content/imAccounts.js
@@ -0,0 +1,696 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2007.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Romain Bezut <romain@bezut.info>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const {interfaces: Ci, utils: Cu} = Components;
+Cu.import("resource:///modules/imServices.jsm");
+Cu.import("resource://gre/modules/DownloadUtils.jsm");
+Cu.import("resource:///modules/iteratorUtils.jsm");
+
+// This is the list of notifications that the account manager window observes
+const events = [
+  "prpl-quit",
+  "account-list-updated",
+  "account-added",
+  "account-updated",
+  "account-removed",
+  "account-connected",
+  "account-connecting",
+  "account-disconnected",
+  "account-disconnecting",
+  "account-connect-progress",
+  "account-connect-error",
+  "autologin-processed",
+  "status-changed",
+  "network:offline-status-changed"
+];
+
+var gAccountManager = {
+  // Sets the delay after connect() or disconnect() during which
+  // it is impossible to perform disconnect() and connect()
+  _disabledDelay: 500,
+  disableTimerID: 0,
+  _connectedLabelInterval: 0,
+  load: function am_load() {
+    this.accountList = document.getElementById("accountlist");
+    let defaultID;
+    Services.core.init(); // ensure the imCore is initialized.
+    for (let acc in this.getAccounts()) {
+      var elt = document.createElement("richlistitem");
+      this.accountList.appendChild(elt);
+      elt.build(acc);
+      if (!defaultID && acc.firstConnectionState == acc.FIRST_CONNECTION_CRASHED)
+        defaultID = acc.id;
+    }
+    for each (let event in events)
+      Services.obs.addObserver(this, event, false);
+    if (!this.accountList.getRowCount())
+      // This is horrible, but it works. Otherwise (at least on mac)
+      // the wizard is not centered relatively to the account manager
+      setTimeout(function() { gAccountManager.new(); }, 0);
+    else {
+      // we have accounts, show the list
+      document.getElementById("accountsDesk").selectedIndex = 1;
+
+      // ensure an account is selected
+      if (defaultID)
+        this.selectAccount(defaultID);
+      else
+        this.accountList.selectedIndex = 0;
+    }
+
+    this.setAutoLoginNotification();
+
+    this.accountList.addEventListener("keypress", this.onKeyPress, true);
+    window.addEventListener("unload", this.unload.bind(this));
+    this._connectedLabelInterval = setInterval(this.updateConnectedLabels, 60000);
+    statusSelector.init();
+  },
+  unload: function am_unload() {
+    clearInterval(this._connectedLabelInterval);
+    for each (let event in events)
+      Services.obs.removeObserver(this, event);
+  },
+  _updateAccountList: function am__updateAccountList() {
+    let accountList = this.accountList;
+    let i = 0;
+    for (let acc in this.getAccounts()) {
+      let oldItem = accountList.getItemAtIndex(i);
+      if (oldItem.id != acc.id) {
+        let accElt = document.getElementById(acc.id);
+        accountList.insertBefore(accElt, oldItem);
+        accElt.restoreItems();
+      }
+      ++i;
+    }
+
+    if (accountList.itemCount == 0) {
+      // Focus the "New Account" button if there are no accounts left.
+      document.getElementById("newaccount").focus();
+      // Return early, otherwise we'll run into an 'undefined property' strict
+      //  warning when trying to focus the buttons. Fixes bug 408.
+      return;
+    }
+
+    // The selected item is still selected
+    accountList.selectedItem.buttons.setFocus();
+    accountList.ensureSelectedElementIsVisible();
+
+    // We need to refresh the disabled menu items
+    this.disableCommandItems();
+  },
+  observe: function am_observe(aObject, aTopic, aData) {
+    if (aTopic == "prpl-quit") {
+      // libpurple is being uninitialized. We don't need the account
+      // manager window anymore, close it.
+      this.close();
+      return;
+    }
+    else if (aTopic == "autologin-processed") {
+      var notification = document.getElementById("accountsNotificationBox")
+                                 .getNotificationWithValue("autoLoginStatus");
+      if (notification)
+        notification.close();
+      return;
+    }
+    else if (aTopic == "network:offline-status-changed") {
+      this.setOffline(aData == "offline");
+      return;
+    }
+    else if (aTopic == "status-changed") {
+      this.setOffline(aObject.statusType == Ci.imIStatusInfo.STATUS_OFFLINE);
+      return;
+    }
+    else if (aTopic == "account-list-updated") {
+      this._updateAccountList();
+      return;
+    }
+
+    // The following notification handlers need an account.
+    aObject.QueryInterface(Ci.imIAccount);
+
+    if (aTopic == "account-added") {
+      document.getElementById("accountsDesk").selectedIndex = 1;
+      var elt = document.createElement("richlistitem");
+      this.accountList.appendChild(elt);
+      elt.build(aObject);
+      if (this.accountList.getRowCount() == 1)
+        this.accountList.selectedIndex = 0;
+    }
+    else if (aTopic == "account-removed") {
+      var elt = document.getElementById(aObject.id);
+      elt.destroy();
+      if (!elt.selected) {
+        this.accountList.removeChild(elt);
+        return;
+      }
+      // The currently selected element is removed,
+      // ensure another element gets selected (if the list is not empty)
+      var selectedIndex = this.accountList.selectedIndex;
+      // Prevent errors if the timer is active and the account deleted
+      clearTimeout(this.disableTimerID);
+      this.disableTimerID = 0;
+      this.accountList.removeChild(elt);
+      var count = this.accountList.getRowCount();
+      if (!count) {
+        document.getElementById("accountsDesk").selectedIndex = 0;
+        return;
+      }
+      if (selectedIndex == count)
+        --selectedIndex;
+      this.accountList.selectedIndex = selectedIndex;
+    }
+    else if (aTopic == "account-updated") {
+      document.getElementById(aObject.id).build(aObject);
+      this.disableCommandItems();
+    }
+    else if (aTopic == "account-connect-progress")
+      document.getElementById(aObject.id).updateConnectionState();
+    else if (aTopic == "account-connect-error")
+      document.getElementById(aObject.id).updateConnectionError();
+    else {
+      const stateEvents = {
+        "account-connected": "connected",
+        "account-connecting": "connecting",
+        "account-disconnected": "disconnected",
+        "account-disconnecting": "disconnecting"
+      };
+      if (aTopic in stateEvents) {
+        let elt = document.getElementById(aObject.id);
+        if (!elt)
+          return; // probably disconnecting a removed account.
+
+        if (aTopic == "account-connecting") {
+          elt.removeAttribute("error");
+          elt.updateConnectionState();
+        }
+        else {
+          if (aTopic == "account-connected")
+            elt.refreshConnectedLabel();
+        }
+
+        elt.setAttribute("state", stateEvents[aTopic]);
+      }
+    }
+  },
+  cancelReconnection: function am_cancelReconnection() {
+    this.accountList.selectedItem.cancelReconnection();
+  },
+  connect: function am_connect() {
+    let account = this.accountList.selectedItem.account;
+    if (account.disconnected) {
+      let disconnect = document.getElementById("cmd_disconnect");
+      disconnect.setAttribute("disabled", "true");
+      this.restoreButtonTimer();
+      account.connect();
+    }
+  },
+  disconnect: function am_disconnect() {
+    let account = this.accountList.selectedItem.account;
+    if (account.connected || account.connecting) {
+      let connect = document.getElementById("cmd_connect");
+      connect.setAttribute("disabled", "true");
+      this.restoreButtonTimer();
+      account.disconnect();
+    }
+  },
+  updateConnectedLabels: function am_updateConnectedLabels() {
+    for (let i = 0; i < gAccountManager.accountList.itemCount; ++i) {
+      let item = gAccountManager.accountList.getItemAtIndex(i);
+      if (item.account.connected)
+        item.refreshConnectedLabel();
+    }
+  },
+  /* This function restores the disabled attribute of the currently visible
+     button (and context menu item) after `this._disabledDelay` ms */
+  restoreButtonTimer: function am_restoreButtonTimer() {
+    clearTimeout(this.disableTimerID);
+    this.accountList.focus();
+    this.disableTimerID = setTimeout(function(aItem) {
+      gAccountManager.disableTimerID = 0;
+      gAccountManager.disableCommandItems();
+      aItem.buttons.setFocus();
+    }, this._disabledDelay, this.accountList.selectedItem);
+  },
+
+  new: function am_new() {
+    this.openDialog("chrome://messenger/content/chat/imAccountWizard.xul");
+  },
+  edit: function am_edit() {
+    // Find the nsIIncomingServer for the current imIAccount.
+    let server = null;
+    let imAccountId = this.accountList.selectedItem.account.numericId;
+    let mgr = Components.classes["@mozilla.org/messenger/account-manager;1"]
+                        .getService(Ci.nsIMsgAccountManager);
+    for each (let account in fixIterator(mgr.accounts, Ci.nsIMsgAccount)) {
+      let incomingServer = account.incomingServer;
+      if (!incomingServer || incomingServer.type != "im")
+        continue;
+      if (incomingServer.wrappedJSObject.imAccount.numericId == imAccountId) {
+        server = incomingServer;
+        break;
+      }
+    }
+
+    let win = Services.wm.getMostRecentWindow("mailnews:accountmanager");
+    if (win) {
+      win.focus();
+      win.selectServer(server);
+    }
+    else {
+      window.openDialog("chrome://messenger/content/AccountManager.xul",
+                        "AccountManager",
+                        "chrome,centerscreen,modal,titlebar,resizable",
+                        { server: server, selectPage: null });
+    }
+  },
+  autologin: function am_autologin() {
+    var elt = this.accountList.selectedItem;
+    elt.autoLogin = !elt.autoLogin;
+  },
+  close: function am_close() {
+    // If a modal dialog is opened, we can't close this window now
+    if (this.modalDialog)
+      setTimeout(function() { window.close();}, 0);
+    else
+      window.close();
+  },
+
+  /* This function disables or enables the currently selected button and
+     the corresponding context menu item */
+  disableCommandItems: function am_disableCommandItems() {
+    let accountList = this.accountList;
+    let selectedItem = accountList.selectedItem;
+    // When opening the account manager, if accounts have errors, we
+    // can be called during build(), before any item is selected.
+    // In this case, just return early.
+    if (!selectedItem)
+      return;
+
+    // If the timer that disables the button (for a short time) already exists,
+    // we don't want to interfere and set the button as enabled.
+    if (this.disableTimerID)
+      return;
+
+    let account = selectedItem.account;
+    let activeCommandName =
+      (this.isOffline || account.disconnected) ? "connect" : "disconnect";
+    let activeCommandElt = document.getElementById("cmd_" + activeCommandName);
+    let isCommandDisabled =
+      (this.isOffline ||
+       (account.disconnected &&
+        account.connectionErrorReason == Ci.imIAccount.ERROR_UNKNOWN_PRPL));
+    
+    [[activeCommandElt, isCommandDisabled],
+     [document.getElementById("cmd_moveup"), accountList.selectedIndex == 0],
+     [document.getElementById("cmd_movedown"),
+      accountList.selectedIndex == accountList.itemCount - 1]
+    ].forEach(function (aEltArray) {
+      let [elt, state] = aEltArray;
+      if (state)
+        elt.setAttribute("disabled", "true");
+      else
+        elt.removeAttribute("disabled");
+    });
+  },
+  onContextMenuShowing: function am_onContextMenuShowing() {
+    let targetElt = document.popupNode;
+    let isAccount = targetElt instanceof Ci.nsIDOMXULSelectControlItemElement;
+    document.getElementById("contextAccountsItems").hidden = !isAccount;
+    if (isAccount) {
+       /* we want to hide either "connect" or "disconnect" depending on the
+          context and we can't use the broadcast of the command element here
+          because the item already observes "contextAccountsItems" */
+      let itemNameToHide, itemNameToShow;
+      if (targetElt.account.disconnected)
+        [itemNameToHide, itemNameToShow] = ["disconnect",  "connect"];
+      else
+        [itemNameToHide, itemNameToShow] = ["connect", "disconnect"];
+      document.getElementById("context_" + itemNameToHide).hidden = true;
+      document.getElementById("context_" + itemNameToShow).hidden = false;
+
+      document.getElementById("context_cancelReconnection").hidden =
+        !targetElt.hasAttribute("reconnectPending");
+    }
+  },
+
+  selectAccount: function am_selectAccount(aAccountId) {
+    this.accountList.selectedItem = document.getElementById(aAccountId);
+    this.accountList.ensureSelectedElementIsVisible();
+  },
+  onAccountSelect: function am_onAccountSelect() {
+    clearTimeout(this.disableTimerID);
+    this.disableTimerID = 0;
+    this.disableCommandItems();
+    // Horrible hack here too, see Bug 177
+    setTimeout(function(aThis) {
+      try {
+        aThis.accountList.selectedItem.buttons.setFocus();
+      } catch (e) {
+        /* Sometimes if the user goes too fast with VK_UP or VK_DOWN, the
+           selectedItem doesn't have the expected binding attached */
+      }
+    }, 0, this);
+  },
+
+  onKeyPress: function am_onKeyPress(event) {
+    if (!this.selectedItem)
+      return;
+
+    if (event.shiftKey &&
+        (event.keyCode == event.DOM_VK_DOWN || event.keyCode == event.DOM_VK_UP)) {
+      let offset = event.keyCode == event.DOM_VK_DOWN ? 1 : -1;
+      gAccountManager.moveCurrentItem(offset);
+      event.stopPropagation();
+      event.preventDefault();
+      return;
+    }
+
+    // As we stop propagation, the default action applies to the richlistbox
+    // so that the selected account is changed with this default action
+    if (event.keyCode == event.DOM_VK_DOWN) {
+      if (this.selectedIndex < this.itemCount - 1)
+        this.ensureIndexIsVisible(this.selectedIndex + 1);
+      event.stopPropagation();
+      return;
+    }
+
+    if (event.keyCode == event.DOM_VK_UP) {
+      if (this.selectedIndex > 0)
+        this.ensureIndexIsVisible(this.selectedIndex - 1);
+      event.stopPropagation();
+      return;
+    }
+
+    if (event.keyCode == event.DOM_VK_RETURN) {
+      let target = event.originalTarget;
+      if (target.localName != "checkbox" &&
+          (target.localName != "button" ||
+           /^(dis)?connect$/.test(target.getAttribute("anonid"))))
+        this.selectedItem.buttons.proceedDefaultAction();
+      return;
+    }
+  },
+
+  moveCurrentItem: function am_moveCurrentItem(aOffset) {
+    let accountList = this.accountList;
+    if (!aOffset || !accountList.selectedItem)
+      return;
+
+    // Create the new preference value from the richlistbox list
+    let items = accountList.children;
+    let selectedID = accountList.selectedItem.id;
+    let array = [];
+    for (let i in items)
+      if (items[i].id != selectedID)
+        array.push(items[i].id);
+
+    let newIndex = accountList.selectedIndex + aOffset;
+    if (newIndex < 0)
+      newIndex = 0;
+    else if (newIndex >= accountList.itemCount)
+      newIndex = accountList.itemCount - 1;
+    array.splice(newIndex, 0, selectedID);
+
+    Services.prefs.setCharPref("messenger.accounts", array.join(","));
+  },
+
+  getAccounts: function am_getAccounts() {
+    let accounts = Services.accounts.getAccounts();
+    while (accounts.hasMoreElements())
+      yield accounts.getNext();
+  },
+
+  openDialog: function am_openDialog(aUrl, aArgs) {
+    this.modalDialog = true;
+    window.openDialog(aUrl, "", "chrome,modal,titlebar,centerscreen", aArgs);
+    this.modalDialog = false;
+  },
+  setAutoLoginNotification: function am_setAutoLoginNotification() {
+    var as = Services.accounts;
+    var autoLoginStatus = as.autoLoginStatus;
+    let isOffline = false;
+    let crashCount = 0;
+    for (let acc in this.getAccounts())
+      if (acc.autoLogin && acc.firstConnectionState == acc.FIRST_CONNECTION_CRASHED)
+        ++crashCount;
+
+    if (autoLoginStatus == as.AUTOLOGIN_ENABLED && crashCount == 0) {
+      let status = Services.core.globalUserStatus.statusType;
+      this.setOffline(isOffline || status == Ci.imIStatusInfo.STATUS_OFFLINE);
+      return;
+    }
+
+    var bundle = document.getElementById("accountsBundle");
+    var box = document.getElementById("accountsNotificationBox");
+    var priority = box.PRIORITY_INFO_HIGH;
+    var connectNowButton = {
+      accessKey: bundle.getString("accountsManager.notification.button.accessKey"),
+      callback: this.processAutoLogin,
+      label: bundle.getString("accountsManager.notification.button.label")
+    };
+    var label;
+
+    switch (autoLoginStatus) {
+      case as.AUTOLOGIN_USER_DISABLED:
+        label = bundle.getString("accountsManager.notification.userDisabled.label");
+        break;
+
+      case as.AUTOLOGIN_SAFE_MODE:
+        label = bundle.getString("accountsManager.notification.safeMode.label");
+        break;
+
+      case as.AUTOLOGIN_START_OFFLINE:
+        label = bundle.getString("accountsManager.notification.startOffline.label");
+        isOffline = true;
+        break;
+
+      case as.AUTOLOGIN_CRASH:
+        label = bundle.getString("accountsManager.notification.crash.label");
+        priority = box.PRIORITY_WARNING_MEDIUM;
+        break;
+
+      /* One or more accounts made the application crash during their connection.
+         If none, this function has already returned */
+      case as.AUTOLOGIN_ENABLED:
+        if (!("PluralForm" in window))
+          Components.utils.import("resource://gre/modules/PluralForm.jsm");
+        label = bundle.getString("accountsManager.notification.singleCrash.label");
+        label = PluralForm.get(crashCount, label).replace("#1", crashCount);
+        priority = box.PRIORITY_WARNING_MEDIUM;
+        connectNowButton.callback = this.processCrashedAccountsLogin;
+        break;
+
+      default:
+        label = bundle.getString("accountsManager.notification.other.label");
+    }
+    let status = Services.core.globalUserStatus.statusType;
+    this.setOffline(isOffline || status == Ci.imIStatusInfo.STATUS_OFFLINE);
+
+    box.appendNotification(label, "autologinStatus", null, priority, [connectNowButton]);
+  },
+  processAutoLogin: function am_processAutoLogin() {
+    var ioService = Services.io;
+    if (ioService.offline) {
+      ioService.manageOfflineStatus = false;
+      ioService.offline = false;
+    }
+
+    Services.accounts.processAutoLogin();
+
+    gAccountManager.accountList.selectedItem.buttons.setFocus();
+  },
+  processCrashedAccountsLogin: function am_processCrashedAccountsLogin() {
+    for (let acc in gAccountManager.getAccounts())
+      if (acc.disconnected && acc.autoLogin &&
+          acc.firstConnectionState == acc.FIRST_CONNECTION_CRASHED)
+        acc.connect();
+
+    let notification = document.getElementById("accountsNotificationBox")
+                               .getNotificationWithValue("autoLoginStatus");
+    if (notification)
+      notification.close();
+
+    gAccountManager.accountList.selectedItem.buttons.setFocus();
+  },
+  setOffline: function am_setOffline(aState) {
+    this.isOffline = aState;
+    if (aState)
+      this.accountList.setAttribute("offline", "true");
+    else
+      this.accountList.removeAttribute("offline");
+    this.disableCommandItems();
+  }
+};
+
+
+let gAMDragAndDrop = {
+  ACCOUNT_MIME_TYPE: "application/x-moz-richlistitem",
+  // Size of the scroll zone on the top and on the bottom of the account list
+  MAGIC_SCROLL_HEIGHT: 20,
+
+  // A preference already exists to define scroll speed, let's use it.
+  get SCROLL_SPEED() {
+    delete this.SCROLL_SPEED;
+    try {
+      this.SCROLL_SPEED =
+        Services.prefs.getIntPref("toolkit.scrollbox.scrollIncrement");
+    }
+    catch (e) {
+      this.SCROLL_SPEED = 20;
+    }
+    return this.SCROLL_SPEED;
+  },
+
+  onDragStart: function amdnd_onDragStart(aEvent, aTransferData, aAction) {
+    let accountElement = aEvent.explicitOriginalTarget;
+    // This stops the dragging session.
+    if (!(accountElement instanceof Ci.nsIDOMXULSelectControlItemElement))
+      throw "Element is not draggable!";
+    if (gAccountManager.accountList.itemCount == 1)
+      throw "Can't drag while there is only one account!";
+
+    // Transferdata is never used, but we need to transfer something.
+    aTransferData.data = new TransferData();
+    aTransferData.data.addDataForFlavour(this.ACCOUNT_MIME_TYPE, accountElement);
+  },
+
+  onDragOver: function amdnd_onDragOver(aEvent, aFlavour, aSession) {
+    let accountElement = aEvent.explicitOriginalTarget;
+    // We are dragging over the account manager, consider it is the same as
+    // the last element.
+    if (accountElement == gAccountManager.accountList)
+      accountElement = gAccountManager.accountList.lastChild;
+
+    // Auto scroll the account list if we are dragging at the top/bottom
+    this.checkForMagicScroll(aEvent.clientY);
+
+    // The hovered element has changed, change the border too
+    if (("_accountElement" in this) && this._accountElement != accountElement)
+      this.cleanBorders();
+
+    if (!aSession.canDrop) {
+      aEvent.dataTransfer.dropEffect = "none";
+      return;
+    }
+    aEvent.dataTransfer.dropEffect = "move";
+
+    if (aEvent.clientY < accountElement.getBoundingClientRect().top +
+                         accountElement.clientHeight / 2) {
+      // we don't want the previous item to show its default bottom-border
+      let previousItem = accountElement.previousSibling;
+      if (previousItem)
+        previousItem.style.borderBottom = "none";
+      accountElement.setAttribute("dragover", "up");
+    }
+    else {
+      if (("_accountElement" in this) &&
+          this._accountElement == accountElement &&
+          accountElement.getAttribute("dragover") == "up")
+        this.cleanBorders();
+      accountElement.setAttribute("dragover", "down");
+    }
+
+    this._accountElement = accountElement;
+  },
+
+  cleanBorders: function amdnd_cleanBorders(aIsEnd) {
+    if (!this._accountElement)
+      return;
+
+    this._accountElement.removeAttribute("dragover");
+    // reset the border of the previous element
+    let previousItem = this._accountElement.previousSibling;
+    if (previousItem) {
+      if (aIsEnd && !previousItem.style.borderBottom && previousItem.previousSibling)
+        previousItem = previousItem.previousSibling;
+      previousItem.style.borderBottom = "";
+    }
+
+    if (aIsEnd)
+      delete this._accountElement;
+  },
+
+  canDrop: function amdnd_canDrop(aEvent, aSession) {
+    let accountElement = aEvent.explicitOriginalTarget;
+    if (accountElement == gAccountManager.accountList)
+      accountElement = gAccountManager.accountList.lastChild;
+    return (accountElement != gAccountManager.accountList.selectedItem);
+  },
+
+  checkForMagicScroll: function amdnd_checkForMagicScroll(aClientY) {
+    let accountList = gAccountManager.accountList;
+    let listSize = accountList.getBoundingClientRect();
+    let direction = 1;
+    if (aClientY < listSize.top + this.MAGIC_SCROLL_HEIGHT)
+      direction = -1;
+    else if (aClientY < listSize.bottom - this.MAGIC_SCROLL_HEIGHT)
+      // We are not on a scroll zone
+      return;
+
+    accountList._scrollbox.scrollTop += direction * this.SCROLL_SPEED;
+  },
+
+  onDrop: function amdnd_onDrop(aEvent, aTransferData, aSession) {
+    let accountElement = aEvent.explicitOriginalTarget;
+    if (accountElement == gAccountManager.accountList)
+      accountElement = gAccountManager.accountList.lastChild;
+
+     if (!aSession.canDrop)
+      return;
+
+    // compute the destination
+    let accountList = gAccountManager.accountList;
+    let offset = accountList.getIndexOfItem(accountElement) -
+                 accountList.selectedIndex;
+    let isDroppingAbove =
+      aEvent.clientY < accountElement.getBoundingClientRect().top +
+                       accountElement.clientHeight / 2;
+    if (offset > 0)
+      offset -= isDroppingAbove;
+    else
+      offset += !isDroppingAbove;
+    gAccountManager.moveCurrentItem(offset);
+  },
+
+  getSupportedFlavours: function amdnd_getSupportedFlavours() {
+    var flavours = new FlavourSet();
+    flavours.appendFlavour(this.ACCOUNT_MIME_TYPE,
+                           "nsIDOMXULSelectControlItemElement");
+    return flavours;
+  }
+};
new file mode 100644
--- /dev/null
+++ b/mail/components/im/content/imAccounts.xul
@@ -0,0 +1,183 @@
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<!-- ***** BEGIN LICENSE BLOCK *****
+   - Version: MPL 1.1/GPL 2.0/LGPL 2.1
+   -
+   - The contents of this file are subject to the Mozilla Public License Version
+   - 1.1 (the "License"); you may not use this file except in compliance with
+   - the License. You may obtain a copy of the License at
+   - http://www.mozilla.org/MPL/
+   -
+   - Software distributed under the License is distributed on an "AS IS" basis,
+   - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+   - for the specific language governing rights and limitations under the
+   - License.
+   -
+   - The Original Code is the Instantbird messenging client, released
+   - 2007.
+   -
+   - The Initial Developer of the Original Code is
+   - Florian QUEZE <florian@instantbird.org>.
+   - Portions created by the Initial Developer are Copyright (C) 2007
+   - the Initial Developer. All Rights Reserved.
+   -
+   - Contributor(s):
+   -   Romain Bezut <romain@bezut.info>
+   -
+   - Alternatively, the contents of this file may be used under the terms of
+   - either the GNU General Public License Version 2 or later (the "GPL"), or
+   - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+   - in which case the provisions of the GPL or the LGPL are applicable instead
+   - of those above. If you wish to allow use of your version of this file only
+   - under the terms of either the GPL or the LGPL, and not to allow others to
+   - use your version of this file under the terms of the MPL, indicate your
+   - decision by deleting the provisions above and replace them with the notice
+   - and other provisions required by the GPL or the LGPL. If you do not delete
+   - the provisions above, a recipient may use your version of this file under
+   - the terms of any one of the MPL, the GPL or the LGPL.
+   -
+   - ***** END LICENSE BLOCK ***** -->
+
+<?xml-stylesheet href="chrome://messenger/content/chat/imAccounts.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/imRichlistbox.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/imAccounts.css" type="text/css"?>
+
+<!DOCTYPE window [
+ <!ENTITY % accountsDTD SYSTEM "chrome://messenger/locale/imAccounts.dtd">
+ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+ <!ENTITY % chatDTD SYSTEM "chrome://messenger/locale/chat.dtd">
+ %accountsDTD;
+ %brandDTD;
+ %chatDTD;
+]>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        id="accountManager"
+        windowtype="Messenger:Accounts"
+        onload="gAccountManager.load()"
+        title="&accountsWindow.title;"
+        style="&accountsWindow.style;"
+        persist="width height screenX screenY">
+ <script type="application/javascript" src="chrome://messenger/content/chat/imAccounts.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/chat/imStatusSelector.js"/>
+ <script type="application/javascript" src="chrome://global/content/nsDragAndDrop.js" />
+ <script type="application/javascript" src="chrome://global/content/nsTransferable.js" />
+
+ <stringbundle id="accountsBundle" src="chrome://messenger/locale/imAccounts.properties"/>
+ <stringbundle id="chatBundle" src="chrome://messenger/locale/chat.properties"/>
+
+ <commandset id="accountsCommands">
+   <command id="cmd_connect"
+            accesskey="&account.connect.accesskey;"
+            label="&account.connect.label;"
+            oncommand="gAccountManager.connect()"/>
+   <command id="cmd_disconnect"
+            label="&account.disconnect.label;"
+            accesskey="&account.disconnect.accesskey;"
+            oncommand="gAccountManager.disconnect()"/>
+   <command id="cmd_cancelReconnection"
+            label="&account.cancelReconnection.label;"
+            accesskey="&account.cancelReconnection.accesskey;"
+            oncommand="gAccountManager.cancelReconnection()"/>
+   <command id="cmd_moveup"
+            label="&account.moveup.label;"
+            oncommand="gAccountManager.moveCurrentItem(-1)"/>
+   <command id="cmd_movedown"
+            label="&account.movedown.label;"
+            oncommand="gAccountManager.moveCurrentItem(1)"/>
+   <command id="cmd_edit"
+            label="&account.edit.label;"
+            accesskey="&account.edit.accesskey;"
+            oncommand="gAccountManager.edit()"/>
+   <command id="cmd_new"
+            label="&accountManager.newAccount.label;"
+            accesskey="&accountManager.newAccount.accesskey;"
+            oncommand="gAccountManager.new()"/>
+   <command id="cmd_close"
+            label="&accountManager.close.label;"
+            accesskey="&accountManager.close.accesskey;"
+            oncommand="gAccountManager.close()"/>
+ </commandset>
+
+ <keyset id="accountsKeys">
+   <key id="key_close1" key="w" modifiers="accel" command="cmd_close"/>
+   <key id="key_close2" keycode="VK_ESCAPE" command="cmd_close"/>
+   <key id="key_close3" command="cmd_close"
+        key="&accountManager.close.commandkey;" modifiers="accel,shift"/>
+ </keyset>
+
+ <broadcasterset>
+   <broadcaster id="contextAccountsItems"/>
+ </broadcasterset>
+
+ <menupopup id="accountsContextMenu"
+            onpopupshowing="gAccountManager.onContextMenuShowing()">
+   <menuitem id="context_connect"
+             command="cmd_connect"
+             observes="contextAccountsItems"/>
+   <menuitem id="context_disconnect"
+             command="cmd_disconnect"
+             observes="contextAccountsItems"/>
+   <menuitem id="context_cancelReconnection"
+             command="cmd_cancelReconnection"
+             observes="contextAccountsItems"/>
+   <menuseparator observes="contextAccountsItems"/>
+   <menuitem command="cmd_new"/>
+   <menuseparator observes="contextAccountsItems"/>
+   <menuitem command="cmd_moveup" observes="contextAccountsItems"/>
+   <menuitem command="cmd_movedown" observes="contextAccountsItems"/>
+   <menuseparator observes="contextAccountsItems"/>
+   <menuitem command="cmd_edit" observes="contextAccountsItems"/>
+ </menupopup>
+
+ <toolbox id="mainToolbox">
+   <toolbar id="statusArea">
+     <stack id="statusImageStack">
+       <image id="userIcon" onclick="statusSelector.userIconClick();"/>
+       <button type="menu" id="statusTypeIcon" status="available">
+         <menupopup id="setStatusTypeMenupopup"
+                    oncommand="statusSelector.editStatus(event);">
+           <menuitem id="statusTypeAvailable" label="&status.available;"
+                     status="available" class="menuitem-iconic"/>
+           <menuitem id="statusTypeUnavailable" label="&status.unavailable;"
+                     status="unavailable" class="menuitem-iconic"/>
+           <menuseparator id="statusTypeOfflineSeparator"/>
+           <menuitem id="statusTypeOffline" label="&status.offline;"
+                     status="offline" class="menuitem-iconic"/>
+         </menupopup>
+       </button>
+     </stack>
+     <stack id="displayNameAndstatusMessageStack" flex="1">
+       <vbox flex="1" pack="center">
+         <label id="displayName" onclick="statusSelector.displayNameClick();"/>
+       </vbox>
+       <label id="statusMessage" crop="end" value=""
+              onclick="statusSelector.statusMessageClick();"/>
+     </stack>
+   </toolbar>
+ </toolbox>
+
+ <deck flex="1" id="accountsDesk" ondblclick="gAccountManager.new();">
+   <vbox flex="1" id="noAccountScreen" align="center" pack="center">
+     <hbox id="noAccountBox" align="top">
+       <image id="noAccountImage"/>
+       <vbox id="noAccountInnerBox" flex="1">
+         <label id="noAccountTitle" value="&accountManager.noAccount.title;"/>
+         <description id="noAccountDesc">&accountManager.noAccount.description;</description>
+       </vbox>
+     </hbox>
+   </vbox>
+   <notificationbox id="accountsNotificationBox" flex="1">
+     <richlistbox id="accountlist" flex="1" context="accountsContextMenu"
+                  onselect="gAccountManager.onAccountSelect();"
+                  ondragstart="nsDragAndDrop.startDrag(event, gAMDragAndDrop);"
+                  ondragover="nsDragAndDrop.dragOver(event, gAMDragAndDrop);"
+                  ondragend="gAMDragAndDrop.cleanBorders(true);"
+                  ondragdrop="nsDragAndDrop.drop(event, gAMDragAndDrop);"/>
+   </notificationbox>
+ </deck>
+ <windowdragbox id="bottombuttons" align="center">
+  <button id="newaccount" command="cmd_new"/>
+  <spacer flex="1"/>
+  <button id="close" command="cmd_close"/>
+ </windowdragbox>
+</window>
new file mode 100644
--- /dev/null
+++ b/mail/components/im/content/imContextMenu.js
@@ -0,0 +1,393 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is
+# Netscape Communications Corporation.
+# Portions created by the Initial Developer are Copyright (C) 1998
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Blake Ross <blake@cs.stanford.edu>
+#   David Hyatt <hyatt@mozilla.org>
+#   Peter Annema <disttsc@bart.nl>
+#   Dean Tessman <dean_tessman@hotmail.com>
+#   Kevin Puetz <puetzk@iastate.edu>
+#   Ben Goodger <ben@netscape.com>
+#   Pierre Chanial <chanial@noos.fr>
+#   Jason Eager <jce2@po.cwru.edu>
+#   Joe Hewitt <hewitt@netscape.com>
+#   Alec Flett <alecf@netscape.com>
+#   Asaf Romano <mozilla.mano@sent.com>
+#   Jason Barnabe <jason_barnabe@fastmail.fm>
+#   Peter Parente <parente@cs.unc.edu>
+#   Giorgio Maone <g.maone@informaction.com>
+#   Tom Germeau <tom.germeau@epigoon.com>
+#   Jesse Ruderman <jruderman@gmail.com>
+#   Joe Hughes <joe@retrovirus.com>
+#   Pamela Greene <pamg.bugs@gmail.com>
+#   Michael Ventnor <ventnors_dogs234@yahoo.com.au>
+#   Simon Bünzli <zeniko@gmail.com>
+#   Gijs Kruitbosch <gijskruitbosch@gmail.com>
+#   Ehsan Akhgari <ehsan.akhgari@gmail.com>
+#   Dan Mosedale <dmose@mozilla.org>
+#   Justin Dolske <dolske@mozilla.com>
+#   Florian Queze <florian@instantbird.org>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+var gChatContextMenu = null;
+
+function imContextMenu(aXulMenu) {
+  this.target            = null;
+  this.menu              = null;
+  this.onLink            = false;
+  this.onMailtoLink      = false;
+  this.onSaveableLink    = false;
+  this.link              = false;
+  this.linkURL           = "";
+  this.linkURI           = null;
+  this.linkProtocol      = null;
+  this.isTextSelected    = false;
+  this.isContentSelected = false;
+  this.shouldDisplay     = true;
+  this.ellipsis = "\u2026";
+
+  try {
+    this.ellipsis =
+      Services.prefs.getComplexValue("intl.ellipsis",
+                                     Ci.nsIPrefLocalizedString).data;
+  } catch (e) { }
+
+  // Initialize new menu.
+  this.initMenu(aXulMenu);
+}
+
+// Prototype for nsContextMenu "class."
+imContextMenu.prototype = {
+  cleanup: function() {
+    let elt = document.getElementById("context-sep-messageactions").nextSibling;
+    // remove the action menuitems added last time we opened the popup
+    while (elt && elt.localName != "menuseparator") {
+      let tmp = elt.nextSibling;
+      elt.parentNode.removeChild(elt);
+      elt = tmp;
+    }
+  },
+
+  // Initialize context menu.
+  initMenu: function CM_initMenu(aPopup) {
+    this.menu = aPopup;
+
+    // Get contextual info.
+    let node = document.popupNode;
+    this.setTarget(node);
+
+    let actions = [];
+    while (node) {
+      if (node._originalMsg) {
+        let msg = node._originalMsg;
+        actions = msg.getActions();
+        break;
+      }
+      node = node.parentNode;
+    }
+
+    this.isTextSelected = this.isTextSelection();
+    this.isContentSelected = this.isContentSelection();
+
+    // Initialize (disable/remove) menu items.
+    // Open/Save/Send link depends on whether we're in a link.
+    var shouldShow = this.onSaveableLink;
+    this.showItem("context-openlink", shouldShow);
+    this.showItem("context-sep-open", shouldShow);
+    this.showItem("context-savelink", shouldShow);
+
+    // Copy depends on whether there is selected text.
+    // Enabling this context menu item is now done through the global
+    // command updating system
+    goUpdateGlobalEditMenuItems();
+
+    this.showItem("context-copy", this.isContentSelected);
+    this.showItem("context-selectall", !this.onLink || this.isContentSelected);
+    this.showItem("context-sep-messageactions", actions.length);
+
+    // Copy email link depends on whether we're on an email link.
+    this.showItem("context-copyemail", this.onMailtoLink);
+
+    // Copy link location depends on whether we're on a non-mailto link.
+    this.showItem("context-copylink", this.onLink && !this.onMailtoLink);
+    this.showItem("context-sep-copylink", this.onLink && this.isContentSelected);
+
+    // Display action menu items.
+    let sep = document.getElementById("context-sep-messageactions");
+    for each (let action in actions) {
+      let menuitem = document.createElement("menuitem");
+      menuitem.setAttribute("label", action.label);
+      menuitem.setAttribute("oncommand", "this.action.run();");
+      menuitem.action = action;
+      sep.parentNode.appendChild(menuitem);
+    }
+  },
+
+  // Set various context menu attributes based on the state of the world.
+  setTarget: function (aNode) {
+
+    // Initialize contextual info.
+    this.onLink            = false;
+    this.linkURL           = "";
+    this.linkURI           = null;
+    this.linkProtocol      = "";
+
+    // Remember the node that was clicked.
+    this.target = aNode;
+
+    // First, do checks for nodes that never have children.
+    // Second, bubble out, looking for items of interest that can have childen.
+    // Always pick the innermost link, background image, etc.
+    const XMLNS = "http://www.w3.org/XML/1998/namespace";
+    var elem = this.target;
+    while (elem) {
+      if (elem.nodeType == Node.ELEMENT_NODE) {
+        // Link?
+        if (!this.onLink &&
+             ((elem instanceof HTMLAnchorElement && elem.href) ||
+              (elem instanceof HTMLAreaElement && elem.href) ||
+              elem instanceof HTMLLinkElement ||
+              elem.getAttributeNS("http://www.w3.org/1999/xlink", "type") == "simple")) {
+
+          // Target is a link or a descendant of a link.
+          this.onLink = true;
+
+          // xxxmpc: this is kind of a hack to work around a Gecko bug (see bug 266932)
+          // we're going to walk up the DOM looking for a parent link node,
+          // this shouldn't be necessary, but we're matching the existing behaviour for left click
+          var realLink = elem;
+          var parent = elem;
+          while ((parent = parent.parentNode) &&
+                 (parent.nodeType == Node.ELEMENT_NODE)) {
+            try {
+              if ((parent instanceof HTMLAnchorElement && parent.href) ||
+                  (parent instanceof HTMLAreaElement && parent.href) ||
+                  parent instanceof HTMLLinkElement ||
+                  parent.getAttributeNS("http://www.w3.org/1999/xlink", "type") == "simple")
+                realLink = parent;
+            } catch (e) { }
+          }
+
+          // Remember corresponding element.
+          this.link = realLink;
+          this.linkURL = this.getLinkURL();
+          this.linkURI = this.getLinkURI();
+          this.linkProtocol = this.getLinkProtocol();
+          this.onMailtoLink = (this.linkProtocol == "mailto");
+          this.onSaveableLink = this.isLinkSaveable(this.link);
+        }
+      }
+
+      elem = elem.parentNode;
+    }
+  },
+
+  // Returns true if clicked-on link targets a resource that can be saved.
+  isLinkSaveable: function(aLink) {
+    return this.linkProtocol && !(
+             this.linkProtocol == "mailto"     ||
+             this.linkProtocol == "javascript" ||
+             this.linkProtocol == "news"       ||
+             this.linkProtocol == "snews"      );
+  },
+
+  // Open linked-to URL in a new window.
+  openLink: function (aURI) {
+    Cc["@mozilla.org/uriloader/external-protocol-service;1"].
+    getService(Ci.nsIExternalProtocolService).
+    loadURI(aURI || this.linkURI, window);
+  },
+
+  // Generate email address and put it on clipboard.
+  copyEmail: function() {
+    // Copy the comma-separated list of email addresses only.
+    // There are other ways of embedding email addresses in a mailto:
+    // link, but such complex parsing is beyond us.
+    var url = this.linkURL;
+    var qmark = url.indexOf("?");
+    var addresses;
+
+    // 7 == length of "mailto:"
+    addresses = qmark > 7 ? url.substring(7, qmark) : url.substr(7);
+
+    // Let's try to unescape it using a character set
+    // in case the address is not ASCII.
+    try {
+      var characterSet = this.target.ownerDocument.characterSet;
+      const textToSubURI = Cc["@mozilla.org/intl/texttosuburi;1"].
+                           getService(Ci.nsITextToSubURI);
+      addresses = textToSubURI.unEscapeURIForUI(characterSet, addresses);
+    }
+    catch(ex) {
+      // Do nothing.
+    }
+
+    var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
+                    getService(Ci.nsIClipboardHelper);
+    clipboard.copyString(addresses);
+  },
+
+  ///////////////
+  // Utilities //
+  ///////////////
+
+  // Show/hide one item (specified via name or the item element itself).
+  showItem: function(aItemOrId, aShow) {
+    var item = aItemOrId.constructor == String ?
+      document.getElementById(aItemOrId) : aItemOrId;
+    if (item)
+      item.hidden = !aShow;
+  },
+
+  // Temporary workaround for DOM api not yet implemented by XUL nodes.
+  cloneNode: function(aItem) {
+    // Create another element like the one we're cloning.
+    var node = document.createElement(aItem.tagName);
+
+    // Copy attributes from argument item to the new one.
+    var attrs = aItem.attributes;
+    for (var i = 0; i < attrs.length; i++) {
+      var attr = attrs.item(i);
+      node.setAttribute(attr.nodeName, attr.nodeValue);
+    }
+
+    // Voila!
+    return node;
+  },
+
+  // Generate fully qualified URL for clicked-on link.
+  getLinkURL: function() {
+    var href = this.link.href;
+    if (href)
+      return href;
+
+    href = this.link.getAttributeNS("http://www.w3.org/1999/xlink",
+                                    "href");
+
+    if (!href || !href.match(/\S/)) {
+      // Without this we try to save as the current doc,
+      // for example, HTML case also throws if empty
+      throw "Empty href";
+    }
+
+    return makeURLAbsolute(this.link.baseURI, href);
+  },
+
+  getLinkURI: function() {
+    try {
+      return Services.io.newURI(this.linkURL, null, null);
+    }
+    catch (ex) {
+     // e.g. empty URL string
+    }
+
+    return null;
+  },
+
+  getLinkProtocol: function() {
+    if (this.linkURI)
+      return this.linkURI.scheme; // can be |undefined|
+
+    return null;
+  },
+
+  // Get text of link.
+  linkText: function() {
+    var text = gatherTextUnder(this.link);
+    if (!text || !text.match(/\S/)) {
+      text = this.link.getAttribute("title");
+      if (!text || !text.match(/\S/)) {
+        text = this.link.getAttribute("alt");
+        if (!text || !text.match(/\S/))
+          text = this.linkURL;
+      }
+    }
+
+    return text;
+  },
+
+  // Get selected text. Only display the first 15 chars.
+  isTextSelection: function() {
+    // Get 16 characters, so that we can trim the selection if it's greater
+    // than 15 chars
+    var selectedText = getBrowserSelection(16);
+
+    if (!selectedText)
+      return false;
+
+    if (selectedText.length > 15)
+      selectedText = selectedText.substr(0,15) + this.ellipsis;
+
+    return true;
+  },
+
+  // Returns true if anything is selected.
+  isContentSelection: function() {
+    return !document.commandDispatcher.focusedWindow.getSelection().isCollapsed;
+  }
+};
+
+/**
+ * Gets the selected text in the active browser. Leading and trailing
+ * whitespace is removed, and consecutive whitespace is replaced by a single
+ * space. A maximum of 150 characters will be returned, regardless of the value
+ * of aCharLen.
+ *
+ * @param aCharLen
+ *        The maximum number of characters to return.
+ */
+function getBrowserSelection(aCharLen) {
+  // selections of more than 150 characters aren't useful
+  const kMaxSelectionLen = 150;
+  const charLen = Math.min(aCharLen || kMaxSelectionLen, kMaxSelectionLen);
+
+  var focusedWindow = document.commandDispatcher.focusedWindow;
+  var selection = focusedWindow.getSelection().toString();
+
+  if (selection) {
+    if (selection.length > charLen) {
+      // only use the first charLen important chars. see bug 221361
+      var pattern = new RegExp("^(?:\\s*.){0," + charLen + "}");
+      pattern.test(selection);
+      selection = RegExp.lastMatch;
+    }
+
+    selection = selection.replace(/^\s+/, "")
+                         .replace(/\s+$/, "")
+                         .replace(/\s+/g, " ");
+
+    if (selection.length > charLen)
+      selection = selection.substr(0, charLen);
+  }
+  return selection;
+}
new file mode 100644
--- /dev/null
+++ b/mail/components/im/content/imStatusSelector.js
@@ -0,0 +1,303 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2007.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+Cu.import("resource:///modules/imStatusUtils.jsm");
+
+var statusSelector = {
+  observe: function ss_observe(aSubject, aTopic, aMsg) {
+    if (aTopic == "status-changed")
+      this.displayCurrentStatus();
+    else if (aTopic == "user-icon-changed")
+      this.displayUserIcon();
+    else if (aTopic == "user-display-name-changed")
+      this.displayUserDisplayName();
+  },
+
+  displayUserIcon: function ss_displayUserIcon() {
+    let icon = Services.core.globalUserStatus.getUserIcon();
+    document.getElementById("userIcon").src = icon ? icon.spec : "";
+  },
+
+  displayUserDisplayName: function ss_displayUserDisplayName() {
+    let displayName = Services.core.globalUserStatus.displayName;
+    let elt = document.getElementById("displayName");
+    if (displayName)
+      elt.removeAttribute("usingDefault");
+    else {
+      let bundle = document.getElementById("chatBundle");
+      displayName = bundle.getString("displayNameEmptyText");
+      elt.setAttribute("usingDefault", displayName);
+    }
+    elt.setAttribute("value", displayName);
+  },
+
+  displayStatusType: function ss_displayStatusType(aStatusType) {
+    document.getElementById("statusMessage")
+            .setAttribute("statusType", aStatusType);
+    let statusString = Status.toLabel(aStatusType);
+    let statusTypeIcon = document.getElementById("statusTypeIcon");
+    statusTypeIcon.setAttribute("status", aStatusType);
+    statusTypeIcon.setAttribute("tooltiptext", statusString);
+    return statusString;
+  },
+
+  displayCurrentStatus: function ss_displayCurrentStatus() {
+    let us = Services.core.globalUserStatus;
+    let status = Status.toAttribute(us.statusType);
+    let message = status == "offline" ? "" : us.statusText;
+    let statusString = this.displayStatusType(status);
+    let statusMessage = document.getElementById("statusMessage");
+    if (message)
+      statusMessage.removeAttribute("usingDefault");
+    else {
+      statusMessage.setAttribute("usingDefault", statusString);
+      message = statusString;
+    }
+    statusMessage.setAttribute("value", message);
+    statusMessage.setAttribute("tooltiptext", message);
+  },
+
+  editStatus: function ss_editStatus(aEvent) {
+    let status = aEvent.originalTarget.getAttribute("status");
+    if (status == "offline")
+      Services.core.globalUserStatus.setStatus(Ci.imIStatusInfo.STATUS_OFFLINE, "");
+    else if (status)
+      this.startEditStatus(status);
+  },
+
+  startEditStatus: function ss_startEditStatus(aStatusType) {
+    let currentStatusType =
+      document.getElementById("statusTypeIcon").getAttribute("status");
+    if (aStatusType != currentStatusType) {
+      this._statusTypeBeforeEditing = currentStatusType;
+      this._statusTypeEditing = aStatusType;
+      this.displayStatusType(aStatusType);
+    }
+    this.statusMessageClick();
+  },
+
+  statusMessageClick: function ss_statusMessageClick() {
+    let statusType =
+      document.getElementById("statusTypeIcon").getAttribute("status");
+    if (statusType == "offline")
+      return;
+
+    let elt = document.getElementById("statusMessage");
+    if (!elt.hasAttribute("editing")) {
+      elt.setAttribute("editing", "true");
+      elt.addEventListener("keypress", this.statusMessageKeyPress);
+      elt.addEventListener("blur", this.statusMessageBlur);
+      if (elt.hasAttribute("usingDefault")) {
+        if ("_statusTypeBeforeEditing" in this &&
+            this._statusTypeBeforeEditing == "offline")
+          elt.setAttribute("value", Services.core.globalUserStatus.statusText);
+        else
+          elt.removeAttribute("value");
+      }
+      if (!("TextboxSpellChecker" in window))
+        Components.utils.import("resource:///modules/imTextboxUtils.jsm");
+      TextboxSpellChecker.registerTextbox(elt);
+      // force binding attachment by forcing layout
+      elt.getBoundingClientRect();
+      elt.select();
+    }
+
+    this.statusMessageRefreshTimer();
+  },
+
+  statusMessageRefreshTimer: function ss_statusMessageRefreshTimer() {
+    const timeBeforeAutoValidate = 20 * 1000;
+    if ("_stopEditStatusTimeout" in this)
+      clearTimeout(this._stopEditStatusTimeout);
+    this._stopEditStatusTimeout = setTimeout(this.finishEditStatusMessage,
+                                             timeBeforeAutoValidate, true);
+  },
+
+  statusMessageBlur: function ss_statusMessageBlur(aEvent) {
+    if (aEvent.originalTarget == document.getElementById("statusMessage").inputField)
+      statusSelector.finishEditStatusMessage(true);
+  },
+
+  statusMessageKeyPress: function ss_statusMessageKeyPress(aEvent) {
+    switch (aEvent.keyCode) {
+      case aEvent.DOM_VK_RETURN:
+      case aEvent.DOM_VK_ENTER:
+        statusSelector.finishEditStatusMessage(true);
+        break;
+
+      case aEvent.DOM_VK_ESCAPE:
+        statusSelector.finishEditStatusMessage(false);
+        break;
+
+      default:
+        statusSelector.statusMessageRefreshTimer();
+    }
+  },
+
+  finishEditStatusMessage: function ss_finishEditStatusMessage(aSave) {
+    clearTimeout(this._stopEditStatusTimeout);
+    delete this._stopEditStatusTimeout;
+    let elt = document.getElementById("statusMessage");
+    if (aSave) {
+      let newStatus = Ci.imIStatusInfo.STATUS_UNKNOWN;
+      if ("_statusTypeEditing" in this) {
+        let statusType = this._statusTypeEditing;
+        if (statusType == "available")
+          newStatus = Ci.imIStatusInfo.STATUS_AVAILABLE;
+        else if (statusType == "unavailable")
+          newStatus = Ci.imIStatusInfo.STATUS_UNAVAILABLE;
+        else if (statusType == "offline")
+          newStatus = Ci.imIStatusInfo.STATUS_OFFLINE;
+        delete this._statusTypeBeforeEditing;
+        delete this._statusTypeEditing;
+      }
+      // apply the new status only if it is different from the current one
+      if (newStatus != Ci.imIStatusInfo.STATUS_UNKNOWN ||
+          elt.value != elt.getAttribute("value"))
+        Services.core.globalUserStatus.setStatus(newStatus, elt.value);
+    }
+    else if ("_statusTypeBeforeEditing" in this) {
+      this.displayStatusType(this._statusTypeBeforeEditing);
+      delete this._statusTypeBeforeEditing;
+      delete this._statusTypeEditing;
+    }
+
+    if (elt.hasAttribute("usingDefault"))
+      elt.setAttribute("value", elt.getAttribute("usingDefault"));
+    TextboxSpellChecker.unregisterTextbox(elt);
+    elt.removeAttribute("editing");
+    elt.removeEventListener("keypress", this.statusMessageKeyPress, false);
+    elt.removeEventListener("blur", this.statusMessageBlur, false);
+  },
+
+  userIconClick: function ss_userIconClick() {
+    const nsIFilePicker = Components.interfaces.nsIFilePicker;
+    let fp = Components.classes["@mozilla.org/filepicker;1"]
+                       .createInstance(nsIFilePicker);
+    let bundle = document.getElementById("chatBundle");
+    fp.init(window, bundle.getString("userIconFilePickerTitle"),
+            nsIFilePicker.modeOpen);
+    fp.appendFilters(nsIFilePicker.filterImages);
+    if (fp.show() == nsIFilePicker.returnOK)
+      Services.core.globalUserStatus.setUserIcon(fp.file);
+  },
+
+  displayNameClick: function ss_displayNameClick() {
+    let elt = document.getElementById("displayName");
+    if (!elt.hasAttribute("editing")) {
+      elt.setAttribute("editing", "true");
+      if (elt.hasAttribute("usingDefault"))
+        elt.removeAttribute("value");
+      elt.addEventListener("keypress", this.displayNameKeyPress);
+      elt.addEventListener("blur", this.displayNameBlur);
+      // force binding attachmant by forcing layout
+      elt.getBoundingClientRect();
+      elt.select();
+    }
+
+    this.displayNameRefreshTimer();
+  },
+
+  _stopEditDisplayNameTimeout: 0,
+  displayNameRefreshTimer: function ss_displayNameRefreshTimer() {
+    const timeBeforeAutoValidate = 20 * 1000;
+    clearTimeout(this._stopEditDisplayNameTimeout);
+    this._stopEditDisplayNameTimeout =
+      setTimeout(this.finishEditDisplayName, timeBeforeAutoValidate, true);
+  },
+
+  displayNameBlur: function ss_displayNameBlur(aEvent) {
+    if (aEvent.originalTarget == document.getElementById("displayName").inputField)
+      statusSelector.finishEditDisplayName(true);
+  },
+
+  displayNameKeyPress: function ss_displayNameKeyPress(aEvent) {
+    switch (aEvent.keyCode) {
+      case aEvent.DOM_VK_RETURN:
+      case aEvent.DOM_VK_ENTER:
+        statusSelector.finishEditDisplayName(true);
+        break;
+
+      case aEvent.DOM_VK_ESCAPE:
+        statusSelector.finishEditDisplayName(false);
+        break;
+
+      default:
+        statusSelector.displayNameRefreshTimer();
+    }
+  },
+
+  finishEditDisplayName: function ss_finishEditDisplayName(aSave) {
+    clearTimeout(this._stopEditDisplayNameTimeout);
+    let elt = document.getElementById("displayName");
+    // Apply the new display name only if it is different from the current one.
+    if (aSave && elt.value != elt.getAttribute("value"))
+      Services.core.globalUserStatus.displayName = elt.value;
+    else if (elt.hasAttribute("usingDefault"))
+      elt.setAttribute("value", elt.getAttribute("usingDefault"));
+
+    elt.removeAttribute("editing");
+    elt.removeEventListener("keypress", this.displayNameKeyPress, false);
+    elt.removeEventListener("blur", this.displayNameBlur, false);
+  },
+
+  init: function ss_load() {
+    let events = ["status-changed"];
+    statusSelector.displayCurrentStatus();
+
+    if (document.getElementById("displayName")) {
+      events.push("user-display-name-changed");
+      statusSelector.displayUserDisplayName();
+    }
+
+    if (document.getElementById("userIcon")) {
+      events.push("user-icon-changed");
+      statusSelector.displayUserIcon();
+    }
+
+    for each (let event in events)
+      Services.obs.addObserver(statusSelector, event, false);
+    statusSelector._events = events;
+
+    window.addEventListener("unload", statusSelector.unload);
+  },
+
+  unload: function ss_unload() {
+    for each (let event in statusSelector._events)
+      Services.obs.removeObserver(statusSelector, event);
+   }
+};
new file mode 100644
--- /dev/null
+++ b/mail/components/im/content/imbuddytooltip.xml
@@ -0,0 +1,418 @@
+<?xml version="1.0"?>
+<!-- ***** BEGIN LICENSE BLOCK *****
+   - Version: MPL 1.1/GPL 2.0/LGPL 2.1
+   -
+   - The contents of this file are subject to the Mozilla Public License Version
+   - 1.1 (the "License"); you may not use this file except in compliance with
+   - the License. You may obtain a copy of the License at
+   - http://www.mozilla.org/MPL/
+   -
+   - Software distributed under the License is distributed on an "AS IS" basis,
+   - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+   - for the specific language governing rights and limitations under the
+   - License.
+   -
+   - The Original Code is the Instantbird messenging client, released
+   - 2007.
+   -
+   - The Initial Developer of the Original Code is
+   - Florian QUEZE <florian@instantbird.org>.
+   - Portions created by the Initial Developer are Copyright (C) 2007
+   - the Initial Developer. All Rights Reserved.
+   -
+   - Contributor(s):
+   -
+   - Alternatively, the contents of this file may be used under the terms of
+   - either the GNU General Public License Version 2 or later (the "GPL"), or
+   - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+   - in which case the provisions of the GPL or the LGPL are applicable instead
+   - of those above. If you wish to allow use of your version of this file only
+   - under the terms of either the GPL or the LGPL, and not to allow others to
+   - use your version of this file under the terms of the MPL, indicate your
+   - decision by deleting the provisions above and replace them with the notice
+   - and other provisions required by the GPL or the LGPL. If you do not delete
+   - the provisions above, a recipient may use your version of this file under
+   - the terms of any one of the MPL, the GPL or the LGPL.
+   -
+   - ***** END LICENSE BLOCK ***** -->
+
+<!DOCTYPE bindings>
+
+<bindings id="buddyTooltipBindings"
+          xmlns="http://www.mozilla.org/xbl"
+          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+          xmlns:xbl="http://www.mozilla.org/xbl"
+          xmlns:html="http://www.w3.org/1999/xhtml">
+
+  <binding id="tooltip" extends="chrome://global/content/bindings/popup.xml#tooltip">
+    <resources>
+      <stylesheet src="chrome://messenger/skin/imBuddytooltip.css"/>
+    </resources>
+    <content noautohide="true" orient="horizontal">
+      <xul:vbox flex="1">
+        <xul:hbox crop="end" align="center" flex="1">
+          <xul:stack class="prplTooltipBuddyIcon">
+            <xul:image class="protoIcon" xbl:inherits="src=iconPrpl,status"/>
+            <xul:image class="statusIcon" xbl:inherits="status"/>
+          </xul:stack>
+          <xul:description xbl:inherits="value=displayname" class="tooltip-header"/>
+        </xul:hbox>
+
+        <xul:grid>
+          <xul:columns>
+            <xul:column/>
+            <xul:column flex="1"/>
+          </xul:columns>
+          <xul:rows anonid="tooltiprows"/>
+        </xul:grid>
+      </xul:vbox>
+
+      <xul:vbox align="end" pack="start" flex="1" style="margin: 0 0; display:block;">
+        <html:img anonid="userIcon"/>
+      </xul:vbox>
+    </content>
+    <implementation implements="nsIObserver, nsIDOMEventListener">
+     <property name="bundle">
+       <getter>
+         <![CDATA[
+          if (!this._bundle)
+            this._bundle = document.getElementById("chatBundle");
+          return this._bundle;
+         ]]>
+       </getter>
+     </property>
+
+     <field name="_buddy">null</field>
+     <property name="buddy" onget="return this._buddy;">
+       <setter>
+         <![CDATA[
+           if (val == this._buddy)
+             return val;
+
+           if (!val)
+             this._buddy.buddy.removeObserver(this);
+           else
+             val.buddy.addObserver(this);
+
+           return (this._buddy = val);
+         ]]>
+       </setter>
+     </property>
+
+     <field name="_contact">null</field>
+     <property name="contact" onget="return this._contact;">
+       <setter>
+         <![CDATA[
+           if (val == this._contact)
+             return val;
+
+           if (!val)
+             this._contact.removeObserver(this);
+           else
+             val.addObserver(this);
+
+           return (this._contact = val);
+         ]]>
+       </setter>
+     </property>
+
+     <method name="handleEvent">
+       <parameter name="aEvent"/>
+       <body>
+       <![CDATA[
+         if (aEvent.type == "DOMAttrModified" && aEvent.attrName == "status") {
+           if (aEvent.attrChange == aEvent.REMOVAL)
+             this.removeAttribute("status");
+           else
+             this.setAttribute("status", aEvent.newValue);
+         }
+        ]]></body>
+      </method>
+
+     <field name="_elt">null</field>
+     <property name="elt">
+       <getter>
+         <![CDATA[
+           return this._elt;
+         ]]>
+       </getter>
+       <setter>
+         <![CDATA[
+           if (val == this._elt)
+             return val;
+
+           if (this._elt)
+             this._elt.removeEventListener("DOMAttrModified", this, false);
+           else
+             val.addEventListener("DOMAttrModified", this);
+           return (this._elt = val);
+         ]]>
+       </setter>
+     </property>
+
+     <property name="rows">
+       <getter>
+         <![CDATA[
+           if (!("_rows" in this)) {
+             this._rows =
+               document.getAnonymousElementByAttribute(this, "anonid",
+                                                       "tooltiprows");
+           }
+           return this._rows;
+         ]]>
+       </getter>
+     </property>
+
+     <method name="setBuddyIcon">
+       <parameter name="aSrc"/>
+       <body>
+       <![CDATA[
+         var img = document.getAnonymousElementByAttribute(this, "anonid",
+                                                           "userIcon");
+         if (aSrc) {
+           const maxSize = 48;
+           img.src = aSrc;
+           var height = img.naturalHeight || maxSize;
+           var width = img.naturalWidth || maxSize;
+           if (height > maxSize || width > maxSize) {
+             var ratio = Math.max(height, width);
+             height = height / ratio * maxSize;
+             width  = width  / ratio * maxSize;
+           }
+           img.parentNode.width = width; //XXXFLo hack to workaround a bug
+           img.height = height;
+           img.parentNode.collapsed = false;
+         }
+         else
+           img.parentNode.collapsed = true;
+       ]]>
+       </body>
+     </method>
+
+     <method name="reset">
+       <body>
+       <![CDATA[
+         var row;
+         while ((row = this.rows.firstChild))
+           this.rows.removeChild(row);
+       ]]>
+       </body>
+     </method>
+
+     <method name="addRow">
+       <parameter name="aLabel"/>
+       <parameter name="aValue"/>
+       <body>
+       <![CDATA[
+         const XULNS =
+           "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+         var row = document.createElementNS(XULNS, "row");
+         var label = document.createElementNS(XULNS, "label");
+         label.className = "header";
+         label.setAttribute("value", aLabel);
+         row.appendChild(label);
+         label = document.createElementNS(XULNS, "description");
+         label.textContent = aValue;
+         row.appendChild(label);
+         row.setAttribute("align", "baseline");
+         this.rows.appendChild(row);
+       ]]>
+       </body>
+     </method>
+
+     <method name="addSeparator">
+       <body>
+       <![CDATA[
+         const XULNS =
+           "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+         var row = document.createElementNS(XULNS, "row");
+         var separator = document.createElementNS(XULNS, "separator");
+         separator.className = "thin";
+         row.appendChild(separator);
+         this.rows.appendChild(row);
+       ]]>
+       </body>
+     </method>
+
+     <method name="updateTooltipFromBuddy">
+       <parameter name="aBuddy"/>
+       <parameter name="aElt"/>
+       <body>
+       <![CDATA[
+         this.buddy = aBuddy;
+         this.elt = aElt;
+
+         this.reset();
+         let name = aBuddy.userName;
+         let displayName = aBuddy.displayName;
+         this.setAttribute("displayname", displayName);
+         let account = aBuddy.account;
+         this.setAttribute("iconPrpl", account.protocol.iconBaseURI + "icon.png");
+         if (aElt.hasAttribute("status"))
+           this.setAttribute("status", aElt.getAttribute("status"));
+         else
+           this.removeAttribute("status");
+         this.setBuddyIcon(aBuddy.buddyIconFilename);
+
+         if (displayName != name)
+           this.addRow(this.bundle.getString("buddytooltip.username"), name);
+
+         this.addRow(this.bundle.getString("buddytooltip.account"), account.name);
+
+         var tooltipInfo = aBuddy.getTooltipInfo();
+         if (tooltipInfo)
+           this.appendTooltipInfo(tooltipInfo);
+         return true;
+       ]]>
+       </body>
+     </method>
+
+     <method name="appendTooltipInfo">
+       <parameter name="aTooltipInfo"/>
+       <body>
+       <![CDATA[
+         while (aTooltipInfo.hasMoreElements()) {
+           var elt =
+             aTooltipInfo.getNext().QueryInterface(Ci.prplITooltipInfo);
+           switch (elt.type) {
+             case Ci.prplITooltipInfo.pair:
+             case Ci.prplITooltipInfo.sectionHeader:
+               this.addRow(elt.label, elt.value);
+               break;
+             case Ci.prplITooltipInfo.sectionBreak:
+               this.addSeparator();
+               break;
+           }
+         }
+       ]]>
+       </body>
+     </method>
+
+     <method name="updateTooltipFromContact">
+       <parameter name="aContact"/>
+       <parameter name="aElt"/>
+       <body>
+       <![CDATA[
+         this.contact = aContact;
+         this.elt = aElt;
+
+         this.updateTooltipFromBuddy(aContact.preferredBuddy.preferredAccountBuddy, aElt);
+
+         return true;
+       ]]>
+       </body>
+     </method>
+
+     <method name="updateTooltipFromConversation">
+       <parameter name="aConv"/>
+       <parameter name="aElt"/>
+       <body>
+       <![CDATA[
+         if (!aConv.isChat && aConv.buddy)
+           return this.updateTooltipFromBuddy(aConv.buddy, aElt);
+
+         this.elt = aElt;
+         this.reset();
+         this.setAttribute("displayname", aConv.name);
+         let account = aConv.account;
+         this.setAttribute("iconPrpl", account.protocol.iconBaseURI + "icon.png");
+         this.removeAttribute("status");
+         this.setBuddyIcon(null);
+         this.addRow(this.bundle.getString("buddytooltip.account"), account.name);
+         return true;
+       ]]>
+       </body>
+     </method>
+
+     <method name="updateTooltipFromParticipant">
+       <parameter name="aChatBuddy"/>
+       <parameter name="aConv"/>
+       <body>
+       <![CDATA[
+         this.reset();
+         let name = aChatBuddy.name;
+         this.setAttribute("displayname", name);
+         let account = aConv.account;
+         this.setAttribute("iconPrpl",
+                           account.protocol.iconBaseURI + "icon.png");
+         this.removeAttribute("status");
+         this.setBuddyIcon(null);
+         this.addRow(this.bundle.getString("buddytooltip.account"),
+                     account.name);
+         this.observedUserInfo = aConv.target.getNormalizedChatBuddyName(name);
+         Services.obs.addObserver(this, "user-info-received", false);
+         account.requestBuddyInfo(this.observedUserInfo);
+         return true;
+       ]]>
+       </body>
+     </method>
+
+     <!-- nsIObserver implementation -->
+     <method name="observe">
+       <parameter name="aSubject"/>
+       <parameter name="aTopic"/>
+       <parameter name="aData"/>
+       <body>
+       <![CDATA[
+         if (aSubject == this.buddy &&
+             (aTopic == "account-buddy-status-changed" ||
+              aTopic == "account-buddy-status-detail-changed" ||
+              aTopic == "account-buddy-display-name-changed" ||
+              aTopic == "account-buddy-icon-changed"))
+           this.updateTooltipFromBuddy(this.buddy, this.elt);
+         else if (aTopic == "contact-preferred-buddy-changed" &&
+                  aSubject.id == this.contact.id) {
+           let buddy = this.contact.preferredBuddy;
+           this.updateTooltipFromBuddy(buddy.preferredAccountBuddy, this.elt);
+         }
+         else if (aTopic == "user-info-received" &&
+                  aData == this.observedUserInfo) {
+           this.appendTooltipInfo(aSubject.QueryInterface(Ci.nsISimpleEnumerator));
+           Services.obs.removeObserver(this, "user-info-received");
+           delete this.observedUserInfo;
+         }
+       ]]>
+       </body>
+     </method>
+    </implementation>
+    <handlers>
+     <handler event="popupshowing">
+       <![CDATA[
+         // No tooltip above the context menu.
+         if (document.popupNode)
+           return false;
+
+         let elt = document.tooltipNode;
+         // No tooltip for elements that have already been removed.
+         if (!elt.parentNode)
+           return false;
+
+         let localName = elt.localName;
+         if (localName == "imconv" && elt.conv)
+           return updateTooltipFromConversation(elt.conv, elt);
+         if (localName == "imcontact")
+           return updateTooltipFromContact(elt.contact, elt);
+         if (localName == "listitem") {
+           let conv = document.getBindingParent(elt).conv;
+           return updateTooltipFromParticipant(elt.chatBuddy, conv);
+         }
+
+         return false;
+       ]]>
+     </handler>
+     <handler event="popuphiding">
+       <![CDATA[
+       this.buddy = null;
+       this.contact = null;
+       this.elt = null;
+       if ("observedUserInfo" in this && this.observedUserInfo) {
+         Services.obs.removeObserver(this, "user-info-received");
+         delete this.observedUserInfo;
+       }
+       ]]>
+     </handler>
+    </handlers>
+  </binding>
+</bindings>
new file mode 100644
--- /dev/null
+++ b/mail/components/im/content/imcontact.xml
@@ -0,0 +1,276 @@
+<?xml version="1.0"?>
+<!-- ***** BEGIN LICENSE BLOCK *****
+   - Version: MPL 1.1/GPL 2.0/LGPL 2.1
+   -
+   - The contents of this file are subject to the Mozilla Public License Version
+   - 1.1 (the "License"); you may not use this file except in compliance with
+   - the License. You may obtain a copy of the License at
+   - http://www.mozilla.org/MPL/
+   -
+   - Software distributed under the License is distributed on an "AS IS" basis,
+   - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+   - for the specific language governing rights and limitations under the
+   - License.
+   -
+   - The Original Code is the Instantbird messenging client, released
+   - 2007.
+   -
+   - The Initial Developer of the Original Code is
+   - Florian QUEZE <florian@instantbird.org>.
+   - Portions created by the Initial Developer are Copyright (C) 2007
+   - the Initial Developer. All Rights Reserved.
+   -
+   - Contributor(s):
+   -
+   - Alternatively, the contents of this file may be used under the terms of
+   - either the GNU General Public License Version 2 or later (the "GPL"), or
+   - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+   - in which case the provisions of the GPL or the LGPL are applicable instead
+   - of those above. If you wish to allow use of your version of this file only
+   - under the terms of either the GPL or the LGPL, and not to allow others to
+   - use your version of this file under the terms of the MPL, indicate your
+   - decision by deleting the provisions above and replace them with the notice
+   - and other provisions required by the GPL or the LGPL. If you do not delete
+   - the provisions above, a recipient may use your version of this file under
+   - the terms of any one of the MPL, the GPL or the LGPL.
+   -
+   - ***** END LICENSE BLOCK ***** -->
+
+<!DOCTYPE bindings [
+  <!ENTITY % chatDTD SYSTEM "chrome://messenger/locale/chat.dtd" >
+  %chatDTD;
+]>
+
+<bindings id="contactBindings"
+          xmlns="http://www.mozilla.org/xbl"
+          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+          xmlns:xbl="http://www.mozilla.org/xbl"
+          xmlns:html="http://www.w3.org/1999/xhtml">
+
+  <binding id="contact" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+    <content>
+      <xul:stack class="prplBuddyIcon" mousethrough="always">
+        <xul:image class="protoIcon" xbl:inherits="src=iconPrpl,status"/>
+        <xul:image class="statusIcon" xbl:inherits="status"/>
+      </xul:stack>
+      <xul:hbox flex="1" class="contact-hbox" mousethrough="always">
+        <xul:label crop="end" flex="1" mousethrough="always"
+                   anonid="displayname" class="contactDisplayName blistDisplayName"
+                   xbl:inherits="value=displayname,status"/>
+        <xul:label crop="end" flex="100000" mousethrough="always"
+                   anonid="statusText" class="contactStatusText"
+                   xbl:inherits="value=statusTextWithDash"/>
+        <xul:button anonid="startChatBubble" class="startChatBubble"
+                    tooltiptext="&openConversationButton.tooltip;"/>
+      </xul:hbox>
+    </content>
+    <implementation implements="nsIObserver">
+
+     <destructor>
+      <![CDATA[
+        if (this.contact) {
+          this.contact.removeObserver(this);
+          delete this.contact;
+        }
+      ]]>
+     </destructor>
+
+     <method name="build">
+      <parameter name="aContact"/>
+      <body>
+      <![CDATA[
+        this.contact = aContact;
+        this.contact.addObserver(this);
+        if (!("Status" in window))
+          Components.utils.import("resource:///modules/imStatusUtils.jsm");
+        this.update();
+      ]]>
+      </body>
+     </method>
+
+     <property name="displayName"
+               onget="return this.contact.displayName;"/>
+
+     <!-- nsIObserver implementation -->
+     <method name="observe">
+       <parameter name="aSubject"/>
+       <parameter name="aTopic"/>
+       <parameter name="aData"/>
+       <body>
+       <![CDATA[
+         if (aTopic == "contact-preferred-buddy-changed" ||
+             aTopic == "contact-display-name-changed" ||
+             aTopic == "contact-status-changed")
+           this.update();
+
+         if (aTopic == "contact-availability-changed" ||
+             aTopic == "contact-display-name-changed")
+           this.group.updateContactPosition(aSubject);
+       ]]>
+       </body>
+     </method>
+
+     <method name="destroy">
+      <body>
+      <![CDATA[
+        this.contact.removeObserver(this);
+        delete this.contact;
+        this.parentNode.removeChild(this);
+      ]]>
+      </body>
+     </method>
+
+     <method name="update">
+      <body>
+      <![CDATA[
+        this.setAttribute("displayname", this.contact.displayName);
+
+        let statusText = this.contact.statusText;
+        if (statusText)
+          statusText = " - " + statusText;
+        this.setAttribute("statusTextWithDash", statusText);
+        let statusType = this.contact.statusType;
+        this.setAttribute("statusText", Status.toLabel(statusType) + statusText);
+        this.setAttribute("status", Status.toAttribute(statusType));
+
+        if (this.contact.canSendMessage)
+          this.setAttribute("cansend", "true");
+        else
+          this.removeAttribute("cansend");
+
+        let proto = this.contact.preferredBuddy.protocol;
+        this.setAttribute("iconPrpl", proto.iconBaseURI + "icon.png");
+      ]]>
+      </body>
+     </method>
+
+     <method name="startAliasing">
+      <body>
+      <![CDATA[
+        if (this.hasAttribute("aliasing"))
+          return; // prevent re-entry.
+
+        this.setAttribute("aliasing", "true");
+        let textbox =
+          document.getAnonymousElementByAttribute(this, "anonid", "displayname");
+        textbox.getBoundingClientRect(); // force binding attachmant by forcing layout
+        textbox.select();
+
+        // Some keys (home/end for example) can make the selected item
+        // of the richlistbox change without producing a blur event on
+        // our textbox. Make sure we watch richlistbox selection changes.
+        this._parentSelectListener = (function(aEvent) {
+          if (aEvent.target == this.parentNode)
+            this.finishAliasing(true);
+        }).bind(this);
+        this.parentNode.addEventListener("select", this._parentSelectListener);
+      ]]>
+      </body>
+     </method>
+
+     <method name="finishAliasing">
+      <parameter name="aSave"/>
+      <body>
+      <![CDATA[
+        if (aSave) {
+          this.contact.alias =
+            document.getAnonymousElementByAttribute(this, "anonid", "displayname").value;
+        }
+        this.removeAttribute("aliasing");
+        this.parentNode.removeEventListener("select", this._parentSelectListener, false);
+        delete this._parentSelectListener;
+        this.parentNode.focus();
+      ]]>
+      </body>
+     </method>
+
+     <method name="remove">
+      <body>
+      <![CDATA[
+        this.contact.remove();
+      ]]>
+      </body>
+     </method>
+
+     <method name="canOpenConversation">
+      <body>
+       <![CDATA[
+         return this.contact.canSendMessage;
+       ]]>
+      </body>
+     </method>
+
+     <method name="openConversation">
+      <body>
+       <![CDATA[
+         this.contact.createConversation();
+       ]]>
+      </body>
+     </method>
+
+     <method name="keyPress">
+      <parameter name="aEvent"/>
+      <body>this._keyPress(aEvent);</body>
+     </method>
+     <method name="_keyPress">
+      <parameter name="aEvent"/>
+      <body>
+      <![CDATA[
+        switch (aEvent.keyCode) {
+          // If Enter or Return is pressed, open a new conversation
+          case aEvent.DOM_VK_RETURN:
+          case aEvent.DOM_VK_ENTER:
+            if (this.hasAttribute("aliasing"))
+              this.finishAliasing(true);
+            else if (this.canOpenConversation())
+              this.openConversation();
+            break;
+
+          case aEvent.DOM_VK_F2:
+            if (!this.hasAttribute("aliasing"))
+              this.startAliasing();
+            break;
+
+          case aEvent.DOM_VK_ESCAPE:
+            if (this.hasAttribute("aliasing"))
+              this.finishAliasing(false);
+            break;
+        }
+      ]]>
+      </body>
+     </method>
+    </implementation>
+    <handlers>
+     <handler event="blur">
+       <![CDATA[
+         if (!this.hasAttribute("aliasing"))
+           return;
+
+         let win =
+           Components.classes["@mozilla.org/focus-manager;1"]
+                     .getService(Components.interfaces.nsIFocusManager)
+                     .activeWindow;
+         if (win == document.defaultView)
+           finishAliasing(true);
+       ]]>
+     </handler>
+
+     <handler event="mousedown">
+       <![CDATA[
+         if (!this.hasAttribute("aliasing") && canOpenConversation() &&
+             event.originalTarget.getAttribute("anonid") == "startChatBubble")
+           openConversation();
+       ]]>
+     </handler>
+
+     <handler event="click">
+       <![CDATA[
+         if (!this.hasAttribute("aliasing") && canOpenConversation() &&
+             event.detail == 2 &&
+             event.originalTarget.getAttribute("anonid") != "expander")
+           openConversation();
+       ]]>
+     </handler>
+    </handlers>
+  </binding>
+</bindings>
new file mode 100644
--- /dev/null
+++ b/mail/components/im/content/imconv.xml
@@ -0,0 +1,228 @@
+<?xml version="1.0"?>
+<!-- ***** BEGIN LICENSE BLOCK *****
+   - Version: MPL 1.1/GPL 2.0/LGPL 2.1
+   -
+   - The contents of this file are subject to the Mozilla Public License Version
+   - 1.1 (the "License"); you may not use this file except in compliance with
+   - the License. You may obtain a copy of the License at
+   - http://www.mozilla.org/MPL/
+   -
+   - Software distributed under the License is distributed on an "AS IS" basis,
+   - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+   - for the specific language governing rights and limitations under the
+   - License.
+   -
+   - The Original Code is the Instantbird messenging client, released
+   - 2007.
+   -
+   - The Initial Developer of the Original Code is
+   - Florian QUEZE <florian@instantbird.org>.
+   - Portions created by the Initial Developer are Copyright (C) 2007
+   - the Initial Developer. All Rights Reserved.
+   -
+   - Contributor(s):
+   -
+   - Alternatively, the contents of this file may be used under the terms of
+   - either the GNU General Public License Version 2 or later (the "GPL"), or
+   - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+   - in which case the provisions of the GPL or the LGPL are applicable instead
+   - of those above. If you wish to allow use of your version of this file only
+   - under the terms of either the GPL or the LGPL, and not to allow others to
+   - use your version of this file under the terms of the MPL, indicate your
+   - decision by deleting the provisions above and replace them with the notice
+   - and other provisions required by the GPL or the LGPL. If you do not delete
+   - the provisions above, a recipient may use your version of this file under
+   - the terms of any one of the MPL, the GPL or the LGPL.
+   -
+   - ***** END LICENSE BLOCK ***** -->
+
+<!DOCTYPE bindings [
+  <!ENTITY % chatDTD SYSTEM "chrome://messenger/locale/chat.dtd" >
+  %chatDTD;
+]>
+
+<bindings id="convBindings"
+          xmlns="http://www.mozilla.org/xbl"
+          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+          xmlns:xbl="http://www.mozilla.org/xbl"
+          xmlns:html="http://www.w3.org/1999/xhtml">
+
+  <binding id="conv" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+    <content>
+      <xul:stack class="prplBuddyIcon" mousethrough="always">
+        <xul:image class="protoIcon" xbl:inherits="src=iconPrpl,status"/>
+        <xul:image class="statusIcon" xbl:inherits="status"/>
+      </xul:stack>
+      <xul:hbox flex="1" class="conv-hbox" mousethrough="always">
+        <xul:label crop="end" flex="1" mousethrough="always"
+                   anonid="displayname" class="convDisplayName blistDisplayName"
+                   xbl:inherits="value=displayname,status"/>
+        <xul:label anonid="unreadCount" class="convUnreadCount"
+                   crop="end" mousethrough="never" flex="1000000"
+                   xbl:inherits="value=unreadCount"/>
+        <xul:button anonid="closeConversationButton" class="closeConversationButton"
+                    tooltiptext="&closeConversationButton.tooltip;"/>
+      </xul:hbox>
+    </content>
+    <implementation implements="nsIObserver">
+
+     <destructor>
+      <![CDATA[
+        if (this.conv) {
+          this.conv.removeObserver(this);
+          delete this.conv;
+        }
+      ]]>
+     </destructor>
+
+     <field name="convView">null</field>
+     <method name="build">
+      <parameter name="aConv"/>
+      <body>
+      <![CDATA[
+        this.conv = aConv;
+        this.conv.addObserver(this);
+        this.update();
+      ]]>
+      </body>
+     </method>
+
+     <property name="displayName"
+               onget="return this.conv.title;"/>
+
+     <!-- for compatibility with the  imgroup sortComparator -->
+     <property name="contact"
+               onget="return this.conv;"/>
+
+     <!-- nsIObserver implementation -->
+     <method name="observe">
+       <parameter name="aSubject"/>
+       <parameter name="aTopic"/>
+       <parameter name="aData"/>
+       <body>
+       <![CDATA[
+         if (aTopic == "target-purple-conversation-changed" ||
+             aTopic == "unread-message-count-changed" ||
+             aTopic == "update-conv-title" ||
+             aTopic == "update-buddy-status" ||
+             aTopic == "update-buddy-status" ||
+             aTopic == "update-conv-chatleft" ||
+             aTopic == "chat-update-topic")
+           this.update();
+         if (aTopic == "update-conv-title")
+           this.group.updateContactPosition(aSubject);
+       ]]>
+       </body>
+     </method>
+
+     <property name="selected">
+       <getter><![CDATA[
+           if (!gChatTab)
+             document.getElementById("tabmail").openTab("chat", {background: true});
+           return gChatTab.tabNode.selected && this.getAttribute("selected") == "true";
+         ]]></getter>
+       <setter><![CDATA[
+         if (val)
+           this.setAttribute("selected", "true");
+         else
+           this.removeAttribute("selected");
+
+         return val;
+         ]]></setter>
+     </property>
+
+     <method name="update">
+      <body>
+      <![CDATA[
+        this.setAttribute("displayname", this.displayName);
+        if (this.selected) {
+          if (this.convView && this.convView.loaded) {
+            this.conv.markAsRead();
+            chatHandler.updateTitle();
+          }
+          this.setAttribute("unreadCount", "");
+        }
+        else {
+          let unreadCount = this.conv.unreadIncomingMessageCount;
+          if (!unreadCount)
+            unreadCount = "";
+          else {
+            if (this.conv.isChat) {
+            unreadCount =
+              this.conv.unreadTargetedMessageCount + "/" + unreadCount;
+            }
+            unreadCount = "(" + unreadCount + ")";
+          }
+          this.setAttribute("unreadCount", unreadCount);
+          chatHandler.updateTitle();
+        }
+
+        if (this.conv.isChat) {
+          if (!this.conv.account.connected || this.conv.left)
+            this.setAttribute("status", "left");
+          else
+            this.removeAttribute("status");
+        }
+        else {
+          let statusType = Ci.imIStatusInfo.STATUS_UNKNOWN;
+          let buddy = this.conv.buddy;
+          if (buddy && buddy.account.connected)
+            statusType = buddy.statusType;
+          if (!("Status" in window))
+            Components.utils.import("resource:///modules/imStatusUtils.jsm");
+          this.setAttribute("status", Status.toAttribute(statusType));
+        }
+
+        this.setAttribute("iconPrpl",
+                          this.conv.account.protocol.iconBaseURI + "icon.png");
+      ]]>
+      </body>
+     </method>
+
+     <method name="destroy">
+      <body>
+      <![CDATA[
+        if (this.conv)
+          this.conv.removeObserver(this);
+        if (this.convView) {
+          this.convView.destroy();
+          this.convView.parentNode.removeChild(this.convView);
+        }
+        let list = this.parentNode;
+        if (list.selectedItem == this)
+          list.selectedItem = this.previousSibling;
+        list.removeChild(this);
+        delete this.conv;
+      ]]>
+      </body>
+     </method>
+
+     <method name="closeConversation">
+      <body>
+       <![CDATA[
+         if (this.conv)
+           this.conv.close();
+         else
+           this.destroy();
+       ]]>
+      </body>
+     </method>
+
+     <method name="keyPress">
+      <parameter name="aEvent"/>
+      <body></body>
+     </method>
+    </implementation>
+    <handlers>
+     <handler event="mousedown">
+       <![CDATA[
+         let anonid = event.originalTarget.getAttribute("anonid");
+         if (anonid == "closeConversationButton") {
+           closeConversation();
+           event.preventDefault();
+         }
+       ]]>
+     </handler>
+    </handlers>
+  </binding>
+</bindings>
new file mode 100644
--- /dev/null
+++ b/mail/components/im/content/imconversation.xml
@@ -0,0 +1,1472 @@
+<?xml version="1.0"?>
+<!-- ***** BEGIN LICENSE BLOCK *****
+   - Version: MPL 1.1/GPL 2.0/LGPL 2.1
+   -
+   - The contents of this file are subject to the Mozilla Public License Version
+   - 1.1 (the "License"); you may not use this file except in compliance with
+   - the License. You may obtain a copy of the License at
+   - http://www.mozilla.org/MPL/
+   -
+   - Software distributed under the License is distributed on an "AS IS" basis,
+   - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+   - for the specific language governing rights and limitations under the
+   - License.
+   -
+   - The Original Code is the Instantbird messenging client, released
+   - 2007.
+   -
+   - The Initial Developer of the Original Code is
+   - Florian QUEZE <florian@instantbird.org>.
+   - Portions created by the Initial Developer are Copyright (C) 2007
+   - the Initial Developer. All Rights Reserved.
+   -
+   - Contributor(s):
+   -   Douglas Thrift <douglas@douglasthrift.net>
+   -
+   - Alternatively, the contents of this file may be used under the terms of
+   - either the GNU General Public License Version 2 or later (the "GPL"), or
+   - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+   - in which case the provisions of the GPL or the LGPL are applicable instead
+   - of those above. If you wish to allow use of your version of this file only
+   - under the terms of either the GPL or the LGPL, and not to allow others to
+   - use your version of this file under the terms of the MPL, indicate your
+   - decision by deleting the provisions above and replace them with the notice
+   - and other provisions required by the GPL or the LGPL. If you do not delete
+   - the provisions above, a recipient may use your version of this file under
+   - the terms of any one of the MPL, the GPL or the LGPL.
+   -
+   - ***** END LICENSE BLOCK ***** -->
+
+
+<!DOCTYPE bindings [
+  <!ENTITY % chatDTD SYSTEM "chrome://messenger/locale/chat.dtd">
+  %chatDTD;
+]>
+
+<bindings id="conversationBindings"
+          xmlns="http://www.mozilla.org/xbl"
+          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+          xmlns:xbl="http://www.mozilla.org/xbl">
+
+  <binding id="conversation">
+    <resources>
+      <stylesheet src="chrome://messenger/skin/chat.css"/>
+    </resources>
+    <content>
+      <xul:vbox class="convBox" flex="1">
+        <xul:hbox class="conv-top" flex="1" anonid="conv-top">
+          <xul:notificationbox class="conv-messages" anonid="convNotificationBox" flex="1" xbl:inherits="chat">
+            <xul:vbox flex="1">
+              <xul:browser anonid="browser" type="content-conversation" flex="1"
+                           xbl:inherits="tooltip=contenttooltip,contextmenu=contentcontextmenu,autoscrollpopup"/>
+              <xul:progressmeter anonid="browserProgress" hidden="true"/>
+              <xul:findbar anonid="FindToolbar" reversed="true"/>
+            </xul:vbox>
+          </xul:notificationbox>
+        </xul:hbox>
+        <xul:splitter class="splitter" anonid="splitter-bottom"/>
+        <xul:vbox anonid="conv-bottom" class="conv-bottom">
+          <xul:textbox anonid="inputBox" class="conv-textbox" multiline="true" flex="1"/>
+        </xul:vbox>
+      </xul:vbox>
+    </content>
+    <implementation implements="nsIObserver">
+     <constructor>
+      <![CDATA[
+       let textbox = this.editor;
+       textbox.addEventListener("keypress", this.inputKeyPress.bind(this));
+       textbox.addEventListener("overflow", this.inputExpand.bind(this), true);
+       textbox.addEventListener("underflow", this._onTextboxUnderflow, true);
+
+       this.getElt("splitter-bottom")
+           .addEventListener("DOMAttrModified",
+                             this._onSplitterChange.bind(this));
+
+       var browser = this.browser;
+       browser.addEventListener("keypress", this.browserKeyPress);
+       browser.addEventListener("dblclick", this.browserDblClick.bind(this));
+       Services.obs.addObserver(this, "conversation-loaded", false);
+
+       // Avoid the browser property setter as the findbar constructor
+       // hasn't been executed yet. The setter will be called by the
+       // constructor in a setTimeout to setup the proper event listeners.
+       this.findbar._browser = browser;
+      ]]>
+     </constructor>
+
+     <destructor>
+      <![CDATA[
+        this.destroy();
+      ]]>
+     </destructor>
+
+     <!-- This is necessary because the destructor doesn't always get
+          called when we are removed from a tabbox.  This needs to be
+          explicitly called before removing the DOM node. -->
+     <method name="destroy">
+       <body>
+         <![CDATA[
+           if (this._conv)
+             this._forgetConv();
+
+           if ("MessageFormat" in window) {
+             let textbox = this.editor;
+             MessageFormat.unregisterTextbox(textbox);
+             TextboxSpellChecker.unregisterTextbox(textbox);
+           }
+         ]]>
+       </body>
+     </method>
+
+     <method name="_forgetConv">
+       <parameter name="aShouldClose"/>
+       <body>
+        <![CDATA[
+           this._conv.removeObserver(this);
+           delete this._conv;
+           this.browser.destroy();
+           this.findbar.destroy();
+        ]]>
+       </body>
+     </method>
+
+     <method name="close">
+       <body>
+        <![CDATA[
+           this._forgetConv(true);
+        ]]>
+       </body>
+     </method>
+
+     <field name="loaded">false</field>
+
+     <field name="_readCount">0</field>
+     <method name="_showFirstMessages">
+      <body>
+      <![CDATA[
+        this.loaded = true;
+        let messages = this._conv.getMessages();
+        this._readCount = messages.length - this._conv.unreadMessageCount;
+        messages.forEach(this.addMsg.bind(this));
+      ]]>
+      </body>
+     </method>
+
+     <field name="_statusText">""</field>
+     <field name="_statusTextEnd">""</field>
+     <field name="_statusTextEndIsError">false</field>
+
+     <method name="addMsg">
+      <parameter name="aMsg"/>
+      <body>
+      <![CDATA[
+        if (!this.loaded)
+          throw "Calling addMsg before the browser is ready?";
+
+        var conv = aMsg.conversation;
+        if (!conv) {
+          // The conversation has already been destroyed,
+          // probably because the window was closed.
+          // Return without doing anything.
+          return;
+        }
+
+        // Ugly hack... :(
+        if (!aMsg.system && conv.isChat) {
+          let name = aMsg.who;
+          let color;
+          if (this._hasBuddy(name)) {
+            let buddy = this.buddies[name];
+            color = buddy.color;
+            buddy.removeAttribute("inactive");
+          }
+          else {
+            // Buddy no longer in the room
+            color = this._computeColor(name);
+          }
+          aMsg.color = "color: hsl(" + color + ", 100%, 40%);";
+        }
+
+        let read = this._readCount > 0;
+        this.browser.appendMessage(aMsg, read);
+        if (read) {
+          --this._readCount;
+          return;
+        }
+
+/*
+        if (aMsg.incoming && !aMsg.system &&
+            (!aMsg.conversation.isChat || aMsg.containsNick) &&
+            Services.prefs.getBoolPref("messenger.options.getAttentionOnNewMessages"))
+          window.getAttention();
+*/
+
+        if (this.tab && this.tab.selected && document.hasFocus())
+          this.tab.update(); // this will mark the conv as read
+
+        if (this.tab && aMsg.incoming && !aMsg.system &&
+            (!this.tab.selected || !document.hasFocus())) {
+          if (conv.isChat && aMsg.containsNick)
+            this.tab.setAttribute("attention", "true");
+          this.tab.setAttribute("unread", "true");
+        }
+      ]]>
+      </body>
+     </method>
+
+     <method name="sendMsg">
+      <parameter name="aMsg"/>
+      <body>
+      <![CDATA[
+        if (!aMsg)
+          return;
+
+        if (aMsg[0] == "/") {
+          // The /say command is used to bypass command processing
+          // (/say can be shortened to just /).
+          // "/say" or "/say " should be ignored, as should "/" and "/ ".
+          if (aMsg.match(/^\/(?:say)? ?$/)) {
+            this.resetInput();
+            return;
+          }
+          else if (aMsg.match(/^\/(?:say)? .*/))
+            aMsg = aMsg.slice(aMsg.indexOf(" ") + 1);
+          else if (Services.cmd.executeCommand(aMsg, this._conv.target)) {
+            this._conv.sendTyping(0);
+            this.resetInput();
+            return;
+          }
+          else if (this._conv.account.protocol.slashCommandsNative) {
+            let cmd = aMsg.match(/^\/[^ ]+/);
+            if (cmd && cmd != "/me") {
+              this._conv.systemMessage(
+                this.bundle.formatStringFromName("unknownCommand", [cmd], 1),
+                true);
+              return;
+            }
+          }
+        }
+
+        let msg = Components.classes["@mozilla.org/txttohtmlconv;1"]
+                            .getService(Ci.mozITXTToHTMLConv)
+                            .scanTXT(aMsg, 0);
+
+        var account = this._conv.account;
+        if (account.noNewlines)
+          // 'Illegal operation on WrappedNative prototype object' if the this
+          // object is not specified (since nsIClassInfo was added to this._conv)
+          msg.split("\n").forEach(this._conv.sendMsg, this._conv);
+        else if (account.HTMLEnabled) {
+          msg = msg.replace(/\n/g, "<br/>");
+          if (Services.prefs.getBoolPref("messenger.conversations.sendFormat")) {
+            let style = MessageFormat.getMessageStyle();
+            let proto = this._conv.account.protocol.id;
+            if (proto == "prpl-msn") {
+              if ("color" in style)
+                msg = "<font color=\"" + style.color + "\">" + msg + "</font>";
+              if ("fontFamily" in style)
+                msg = "<font face=\"" + style.fontFamily + "\">" + msg + "</font>";
+              // MSN doesn't support font size info in messages...
+            }
+            else if (proto == "prpl-aim" || proto == "prpl-icq" ||
+                     proto == "prpl-yahoo" || proto == "prpl-yahoojp") {
+              let styleAttributes = ""
+              if ("color" in style)
+                styleAttributes += " color=\"" + style.color + "\"";
+              if ("fontFamily" in style)
+                styleAttributes += " face=\"" + style.fontFamily + "\"";
+              if ("fontSize" in style) {
+                let size = style.fontSize - style.defaultFontSize;
+                if (size < -4)
+                  size = 1;
+                else if (size < 0)
+                  size = 2;
+                else if (size < 3)
+                  size = 3
+                else if (size < 7)
+                  size = 4;
+                else if (size < 15)
+                  size = 5;
+                else if (size < 25)
+                  size = 6;
+                else
+                  size = 7;
+                styleAttributes += " size=\"" + size + "\""
+                                 + " style=\"font-size: " + style.fontSize + "px;\"";
+              }
+              if (styleAttributes)
+                msg = "<font" + styleAttributes + ">" + msg + "</font>";
+            }
+            else {
+              let styleProperties = [];
+              if ("color" in style)
+                styleProperties.push("color: " + style.color);
+              if ("fontFamily" in style)
+                styleProperties.push("font-family: " + style.fontFamily);
+              if ("fontSize" in style)
+                styleProperties.push("font-size: " + style.fontSize + "px");
+              style = styleProperties.join("; ");
+              if (style)
+                msg = "<span style=\"" + style + "\">" + msg + "</span>";
+            }
+          }
+          this._conv.sendMsg(msg);
+        }
+        else
+          this._conv.sendMsg(account.HTMLEscapePlainText ? msg : aMsg);
+        // reset the textbox to its original size
+        this.resetInput();
+      ]]>
+      </body>
+     </method>
+
+     <method name="_onSplitterChange">
+      <parameter name="aEvent"/>
+      <body>
+      <![CDATA[
+        if (aEvent.attrName != "state" || aEvent.prevValue != "dragging")
+          return;
+
+        let textbox = this.editor;
+        // set the default height as the deck height (modified by the splitter)
+        textbox.defaultHeight = parseInt(textbox.parentNode.height) -
+          this._TEXTBOX_VERTICAL_OVERHEAD;
+      ]]>
+      </body>
+     </method>
+
+     <!--
+      This value represents the difference between the deck's height and the
+      textbox's content height (borders, margins, paddings).
+      Differ according to the Operating System native theme.
+     -->
+     <field name="_TEXTBOX_VERTICAL_OVERHEAD">0</field>
+     <!--
+       Ratio textbox height / conversation height.
+       0.1 means that the textbox's height is 10% of the conversation's height.
+     -->
+     <field name="_TEXTBOX_RATIO" readonly="true">0.1</field>
+
+
+     <method name="calculateTextboxDefaultHeight">
+      <body>
+      <![CDATA[
+        let totalSpace = parseInt(window.getComputedStyle(this, null)
+                                        .getPropertyValue("height"));
+        let textbox = this.editor;
+        let textboxStyle = window.getComputedStyle(textbox, null);
+        let lineHeight = parseInt(textboxStyle.getPropertyValue("line-height"));
+
+        // Compute the overhead size.
+        let textboxHeight = textbox.inputField.clientHeight;
+        let deckHeight = textbox.parentNode.boxObject.height;
+        this._TEXTBOX_VERTICAL_OVERHEAD = deckHeight - textboxHeight;
+
+        // Calculate the number of lines to display.
+        let numberOfLines =
+          Math.round(totalSpace * this._TEXTBOX_RATIO / lineHeight);
+        if (numberOfLines <= 0)
+          numberOfLines = 1;
+
+        if (!this._maxEmptyLines) {
+          this._maxEmptyLines =
+            Services.prefs.getIntPref("messenger.conversations.textbox.defaultMaxLines");
+        }
+
+        if (numberOfLines > this._maxEmptyLines)
+          numberOfLines = this._maxEmptyLines;
+        textbox.defaultHeight = numberOfLines * lineHeight;
+
+        // set minimum height (in case the user moves the splitter)
+        textbox.parentNode.minHeight =
+          lineHeight + this._TEXTBOX_VERTICAL_OVERHEAD;
+      ]]>
+      </body>
+     </method>
+
+     <method name="initTextboxFormat">
+      <body>
+      <![CDATA[
+        let textbox = this.editor;
+
+        if (!("MessageFormat" in window))
+          Components.utils.import("resource:///modules/imTextboxUtils.jsm");
+        MessageFormat.registerTextbox(textbox);
+
+        // Init the textbox size
+        this.calculateTextboxDefaultHeight();
+        textbox.parentNode.height = textbox.defaultHeight +
+                                    this._TEXTBOX_VERTICAL_OVERHEAD;
+        textbox.inputField.style.overflowY = "hidden";
+
+        // Delay the initialization of the spellchecker until after
+        // the checkbox is initialized, otherwise the spellchecker is
+        // broken in conversations added to the window before it is
+        // visible (bug 295).
+        TextboxSpellChecker.registerTextbox(textbox);
+      ]]>
+      </body>
+     </method>
+
+     <method name="inputKeyPress">
+      <parameter name="event"/>
+      <body>
+      <![CDATA[
+        // shift + page up/down should scroll the browser.
+        if (event.shiftKey && (event.keyCode == KeyEvent.DOM_VK_PAGE_UP ||
+                               event.keyCode == KeyEvent.DOM_VK_PAGE_DOWN)) {
+
+          let direction = (event.keyCode == KeyEvent.DOM_VK_PAGE_UP) ? -1 : 1;
+          this.browser.docShell
+              .QueryInterface(Components.interfaces.nsITextScroll)
+              .scrollByPages(direction);
+
+          event.preventDefault();
+          return;
+        }
+
+        var inputBox = this.editor;
+        // When attempting to copy an empty selection, copy the
+        // browser selection instead (see bug 693).
+        // The 'C' won't be lowercase if caps lock is enabled.
+        if ((event.charCode == 99 /* 'c' */ ||
+             (event.charCode == 67 /* 'C' */ && !event.shiftKey)) &&
+#ifndef XP_MACOSX
+            event.ctrlKey &&
+#else
+            event.metaKey &&
+#endif
+            inputBox.selectionStart == inputBox.selectionEnd) {
+          this.browser.doCommand();
+          return;
+        }
+
+        let text = inputBox.value;
+
+        // Keep the default behavior of the tab key if the input box
+        // is empty or a modifier is used.
+        if (event.keyCode == KeyEvent.DOM_VK_TAB && text.length != 0 &&
+            !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey) {
+          event.preventDefault();
+
+          let completions = [];
+          let firstWordSuffix = " ";
+          let word = text.substring(0, inputBox.selectionStart).match(/\S*$/)[0];
+          if (!word)
+            return;
+
+          let isFirstWord = inputBox.selectionStart == word.length;
+          // check if we are completing a command.
+          let completingCommand = isFirstWord && word[0] == "/";
+          if (completingCommand) {
+            for each (let cmd in Services.cmd.listCommandsForConversation(this._conv)) {
+              // It's possible to have a global and a protocol specific command
+              // with the same name. Avoid duplicates in the |completions| array.
+              let name = "/" + cmd.name;
+              if (completions.indexOf(name) == -1)
+                completions.push(name);
+            }
+          }
+          else {
+            // If it's not a command, the only thing we can complete is a nick.
+            if (!this._conv.isChat)
+              return;
+
+            // Twitter username mentions start with "@".
+            // TODO: find a less prpl-specific way to handle username prefixes.
+            if (this._conv.account.protocol.id == "prpl-twitter") {
+              if (word[0] != "@")
+                return;
+              word = word.substring(1);
+            }
+            else
+              firstWordSuffix = ": ";
+
+            completions = Object.keys(this.buddies);
+            let outgoingNick = this._conv.nick;
+            completions = completions.filter(function(c) c != outgoingNick);
+          }
+
+          // Keep only the completions that share |word| as a prefix.
+          // Be case insensitive only if |word| is entirely lower case.
+          let condition;
+          if (word.toLocaleLowerCase() == word)
+            condition = function(c) c.toLocaleLowerCase().indexOf(word) == 0;
+          else
+            condition = function(c) c.indexOf(word) == 0;
+          completions = completions.filter(condition);
+          if (completions.length == 0)
+            return;
+
+          if (this._conv.isChat && !completingCommand) {
+            // If only one of the possible completions is an active nick, take it.
+            let activeNick = null;
+            for each (let c in completions) {
+              if (this._hasBuddy(c) &&
+                  !this.buddies[c].hasAttribute("inactive")) {
+                if (!activeNick)
+                  activeNick = c;
+                else {
+                  activeNick = null;
+                  break;
+                }
+              }
+            }
+            if (activeNick)
+              completions = [activeNick];
+          }
+
+          // Only one possible completion? Apply it! :-)
+          // Replace what the user typed as its upper/lowercase may not be correct.
+          if (completions.length == 1) {
+            inputBox.selectionStart -= word.length;
+            this.addString(completions[0] + (isFirstWord ? firstWordSuffix : " "));
+            return;
+          }
+
+          // We have several possible completions, attempt to find a common prefix.
+          completions.sort();
+          let i = 0;
+          let maxLength = Math.min(completions[0].length,
+                                   completions[completions.length - 1].length);
+          while (i < maxLength &&
+                 completions[0][i] == completions[completions.length - 1][i])
+            ++i;
+
+          // Apply the common prefix, if any.
+          if (i > 0) {
+            inputBox.selectionStart -= word.length;
+            this.addString(completions[0].substring(0, i));
+          }
+
+          // Finally, display the possible completions in a system message.
+          this._conv.systemMessage(completions.join(" "));
+          return;
+        }
+
+        // Handle typing notifications / character count.
+        if (event.keyCode != 13) {
+          setTimeout((function () {
+            // By the time the timeout is executed, the conversation may have
+            // been closed.
+            if (!this._conv)
+              return;
+
+            let text = inputBox.value;
+
+            let maxLength = this._conv.account.maxMessageLength;
+            if (maxLength) {
+              let left = maxLength - text.length;
+              // 200 is a 'magic' constant to avoid showing big numbers.
+              this._statusTextEnd =
+                left < Math.min(200, maxLength) ? left.toString() : "";
+              this._statusTextEndIsError = left < 0;
+              this.displayStatusText();
+            }
+
+            // try to avoid sending typing notifications when the user is
+            // typing a command in the conversation.
+            // These checks are not perfect (especially if non-existing
+            // commands are sent as regular messages on the in-use prpl).
+            if (! /^\//.test(text))
+              this._conv.sendTyping(text.length);
+            else
+              if (/^\/me /.test(text))
+                this._conv.sendTyping(text.length - 4);
+          }).bind(this), 0);
+          return;
+        }
+
+        if (!event.ctrlKey && !event.shiftKey && !event.altKey) {
+          // Prevent the default action before calling sendMsg to avoid having
+          // a line break inserted in the textbox if sendMsg throws.
+          event.preventDefault();
+          this.sendMsg(text);
+        }
+        else if (!event.shiftKey)
+          this.addString("\n");
+      ]]>
+      </body>
+     </method>
+
+     <method name="resetInput">
+      <body>
+      <![CDATA[
+        var inputBox = this.editor;
+        inputBox.value = "";
+        this._statusTextEnd = "";
+        this._statusTextEndIsError = false;
+        this.displayStatusText();
+
+        let overflow = "";
+        if (TextboxSize.autoResize) {
+          let currHeight = parseInt(inputBox.parentNode.height);
+          if (inputBox.defaultHeight + this._TEXTBOX_VERTICAL_OVERHEAD > currHeight)
+            inputBox.defaultHeight = currHeight - this._TEXTBOX_VERTICAL_OVERHEAD;
+          this.getElt("conv-bottom").height =
+            inputBox.defaultHeight + this._TEXTBOX_VERTICAL_OVERHEAD;
+          overflow = "hidden";
+        }
+
+        inputBox.inputField.style.overflowY = overflow;
+      ]]>
+      </body>
+     </method>
+
+     <method name="inputExpand">
+      <parameter name="event"/>
+      <body>
+      <![CDATA[
+        let textbox = this.editor;
+        let input = textbox.inputField;
+
+        // This feature has been disabled, or the user is currently dragging
+        // the splitter and the textbox has received an overflow event
+        if (!TextboxSize.autoResize ||
+            this.getElt("splitter-bottom").getAttribute("state") == "dragging") {
+          input.style.overflowY = "";
+          return;
+        }
+
+        // Check whether we can increase the height without hidding the status bar
+        // (ensure the min-height property on the top part of this dialog)
+        let topBox = this.getElt("conv-top");
+        let topBoxStyle = window.getComputedStyle(topBox, null);
+        let topMinSize = parseInt(topBoxStyle.getPropertyValue("min-height"));
+        let topSize = parseInt(topBoxStyle.getPropertyValue("height"));
+        let deck = textbox.parentNode;
+        let oldDeckHeight = parseInt(deck.height);
+        let newDeckHeight =
+          parseInt(input.scrollHeight) + this._TEXTBOX_VERTICAL_OVERHEAD;
+
+        if (!topMinSize || topSize - topMinSize > newDeckHeight - oldDeckHeight) {
+          // Hide a possible vertical scrollbar.
+          input.style.overflowY = "hidden";
+          deck.height = newDeckHeight;
+        }
+        else {
+          input.style.overflowY = "";
+          // Set it to the maximum possible value.
+          deck.height = oldDeckHeight + (topSize - topMinSize);
+        }
+      ]]>
+      </body>
+     </method>
+
+     <method name="onConvResize">
+      <body>
+      <![CDATA[
+        let splitter = this.getElt("splitter-bottom");
+        let textbox = this.editor;
+
+        if (!splitter.hasAttribute("state")) {
+          this.calculateTextboxDefaultHeight();
+          textbox.parentNode.height = textbox.defaultHeight +
+                                      this._TEXTBOX_VERTICAL_OVERHEAD;
+        }
+        else {
+          // Used in case the browser is already on its min-height, resize the
+          // textbox to avoid hidding the status bar.
+          let convTop = this.getElt("conv-top");
+          let convTopStyle = window.getComputedStyle(convTop, null);
+          let convTopHeight = parseInt(convTopStyle.getPropertyValue("height"));
+          let convTopMinHeight =
+            parseInt(convTopStyle.getPropertyValue("min-height"));
+
+          if (convTopHeight == convTopMinHeight) {
+            textbox.parentNode.height = parseInt(textbox.parentNode.minHeight);
+            convTopHeight = parseInt(convTopStyle.getPropertyValue("height"));
+            textbox.parentNode.height = parseInt(textbox.parentNode.minHeight) +
+                                        (convTopHeight - convTopMinHeight);
+          }
+        }
+
+        if (TextboxSize.autoResize)
+          this.inputExpand();
+      ]]>
+      </body>
+     </method>
+
+     <method name="_onTextboxUnderflow">
+      <parameter name="event"/>
+      <body>
+      <![CDATA[
+        if (TextboxSize.autoResize)
+          this.inputField.style.overflowY = "hidden";
+      ]]>
+      </body>
+     </method>
+
+     <method name="browserKeyPress">
+     <parameter name="event"/>
+      <body>
+      <![CDATA[
+#ifndef XP_MACOSX
+        var accelKeyPressed = event.ctrlKey;
+#else
+        var accelKeyPressed = event.metaKey;
+#endif
+        // 118 is the decimal code for "v" character, 13 keyCode for "return" key
+        if (((accelKeyPressed && event.charCode != 118) || event.altKey) &&
+            event.keyCode != 13)
+          return;
+
+        if (event.charCode == 0 &&  // it's not a character, it's a command key
+            (event.keyCode != 13 && // Return
+             event.keyCode != 8 &&  // Backspace
+             event.keyCode != 46))  // Delete
+          return;
+
+        if (accelKeyPressed ||
+            !Services.prefs.getBoolPref("accessibility.typeaheadfind"))
+          document.getBindingParent(this).editor.focus();
+
+        // Returns for Ctrl+V
+        if (accelKeyPressed)
+          return;
+
+        const masks = Components.interfaces.nsIDOMNSEvent;
+        var modifiers = 0;
+        if (event.shiftKey)
+          modifiers |= masks.SHIFT_MASK;
+        if (event.ctrlKey)
+          modifiers |= masks.CONTROL_MASK;
+        if (event.altKey)
+          modifiers |= masks.ALT_MASK;
+        if (event.metaKey)
+          modifiers |= masks.META_MASK;
+        if (event.accelKey)
+          modifiers |= (navigator.platform.indexOf("Mac") >= 0) ? masks.META_MASK
+                                                                : masks.CONTROL_MASK;
+
+        // resend the event
+        window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+              .getInterface(Components.interfaces.nsIDOMWindowUtils)
+              .sendKeyEvent(event.type, event.keyCode, event.charCode, modifiers);
+      ]]>
+      </body>
+     </method>
+
+     <method name="browserDblClick">
+     <parameter name="event"/>
+      <body>
+      <![CDATA[
+        if (!Services.prefs.getBoolPref("messenger.conversations.doubleClickToReply"))
+          return;
+
+        for (let node = event.target; node; node = node.parentNode) {
+          if (node._originalMsg) {
+            let msg = node._originalMsg;
+            let actions = msg.getActions();
+            if (actions.length >= 1) {
+              actions[0].run();
+              return;
+            }
+            if (msg.system || msg.outgoing || !msg.incoming || msg.error ||
+                !this._conv.isChat)
+              return;
+            this.addPrompt(msg.who + ": ");
+            return;
+          }
+        }
+      ]]>
+      </body>
+     </method>
+
+     <!-- Replace the current selection in the editor by the given string -->
+     <method name="addString">
+       <parameter name="aString"/>
+       <body>
+       <![CDATA[
+         var editor = this.editor;
+         var length = (aString != "")
+                      ? aString.length
+                      : 0;
+
+         var cursorPosition = editor.selectionStart + length;
+
+         editor.value = editor.value.substr(0, editor.selectionStart) + aString +
+                        editor.value.substr(editor.selectionEnd);
+         editor.selectionStart = editor.selectionEnd = cursorPosition;
+       ]]>
+       </body>
+     </method>
+
+     <method name="addPrompt">
+       <parameter name="aPrompt"/>
+       <body>
+       <![CDATA[
+         let editor = this.editor;
+         let currentEditorValue = editor.value;
+         if (currentEditorValue.indexOf(aPrompt) != 0)
+           editor.value = aPrompt + currentEditorValue;
+         editor.focus();
+       ]]>
+       </body>
+     </method>
+
+     <!-- Update the participant count of a chat conversation -->
+     <method name="updateParticipantCount">
+       <body>
+       <![CDATA[
+         document.getElementById("participantCount").value =
+           Object.keys(this.buddies).length;
+       ]]>
+       </body>
+     </method>
+
+     <!-- Set the attributes (flags) of a chat buddy -->
+     <method name="setBuddyAttributes">
+       <parameter name="aItem"/>
+       <body>
+       <![CDATA[
+         var buddy = aItem.chatBuddy;
+         var image;
+         if (!buddy.noFlags) {
+           if (buddy.op)
+             image = "operator";
+           else if (buddy.halfOp)
+             image = "half-operator";
+           else if (buddy.voiced)
+             image = "voice";
+           else if (buddy.founder)
+             image = "founder";
+         }
+         if (image)
+           aItem.setAttribute("image", "chrome://messenger/skin/" + image + ".png");
+         else
+           aItem.removeAttribute("image");
+       ]]>
+       </body>
+     </method>
+
+     <!-- compute color for a nick -->
+     <method name="_computeColor">
+       <parameter name="aName"/>
+       <body>
+       <![CDATA[
+         // Compute the color based on the nick
+         var nick = aName.match(/[a-zA-Z0-9]+/);
+         nick = nick ? nick[0].toLowerCase() : nick = aName;
+         // We compute a hue value (between 0 and 359) based on the
+         // characters of the nick.
+         // The first character weights kInitialWeight, each following
+         // character weights kWeightReductionPerChar * the weight of the
+         // previous character.
+         const kInitialWeight = 10; // 10 = 360 hue values / 36 possible characters.
+         const kWeightReductionPerChar = 0.52; // arbitrary value
+         var weight = kInitialWeight;
+         var res = 0;
+         for (var i = 0; i < nick.length; ++i) {
+           var char = nick.charCodeAt(i) - 47;
+           if (char > 10)
+             char -= 39;
+           // now char contains a value between 1 and 36
+           res += char * weight;
+           weight *= kWeightReductionPerChar;
+         }
+         return Math.round(res) % 360;
+       ]]>
+       </body>
+     </method>
+
+     <!-- Test if aBuddyName is known in this.buddies without
+          risking a conflict with an hypothetical hasOwnProperty nick. -->
+     <method name="_hasBuddy">
+       <parameter name="aBuddyName"/>
+       <body>
+       <![CDATA[
+         return Object.prototype.hasOwnProperty.call(this.buddies, aBuddyName);
+       ]]>
+       </body>
+     </method>
+
+     <!-- Add a buddy in the visible list of participants -->
+     <method name="addBuddy">
+       <parameter name="aBuddy"/>
+       <body>
+       <![CDATA[
+         var name = aBuddy.name;
+         if (this._hasBuddy(name))
+           throw "Adding a chat buddy twice?!";
+         var item = document.createElement("listitem");
+         item.chatBuddy = aBuddy;
+         item.setAttribute("class", "listitem-iconic");
+         item.setAttribute("label", name);
+         this.setBuddyAttributes(item);
+
+         var color = this._computeColor(name);
+         var style = "color: hsl(" + color + ", 100%, 40%);";
+         item.setAttribute("style", style);
+         item.setAttribute("inactive", "true");
+         item.color = color;
+         this.buddies[name] = item;
+
+         // Insert item at the right position
+         this.addNick(item);
+       ]]>
+       </body>
+     </method>
+
+     <method name="addNick">
+       <parameter name="aListItem"/>
+       <body>
+       <![CDATA[
+         var nicklist = document.getElementById("nicklist");
+         var nick = aListItem.getAttribute("label").toLowerCase();
+
+         // Look for the place of the nick in the list
+         var start = 0;
+         var end = nicklist.itemCount;
+         while (start < end) {
+           var middle = start + Math.floor((end - start) / 2);
+           if (nick < nicklist.getItemAtIndex(middle)
+                              .getAttribute("label").toLowerCase())
+             end = middle;
+           else
+             start = middle + 1;
+         }
+
+         // Now insert the element
+         if (end == nicklist.itemCount)
+           nicklist.appendChild(aListItem);
+         else
+           nicklist.insertBefore(aListItem, nicklist.getItemAtIndex(end));
+       ]]>
+       </body>
+     </method>
+
+     <!-- Update a buddy in the visible list of participants -->
+     <method name="updateBuddy">
+       <parameter name="aBuddy"/>
+       <parameter name="aOldName"/>
+       <body>
+       <![CDATA[
+         var name = aBuddy.name;
+         if (!aOldName) {
+           // If aOldName is null, we are changing the flags of the buddy
+           var item = this.buddies[name];
+           item.chatBuddy = aBuddy;
+           this.setBuddyAttributes(item);
+           return;
+         }
+
+         // Is aOldName is not null, then we are renaming the buddy
+         if (!this._hasBuddy(aOldName))
+           throw "Updating a chat buddy that does not exist?!";
+
+         if (this._hasBuddy(name))
+           throw "Updating a chat buddy to an already existing one?!";
+
+         var item = this.buddies[aOldName];
+         item.chatBuddy = aBuddy;
+         delete this.buddies[aOldName];
+         this.buddies[name] = item;
+         item.setAttribute("label", name);
+
+         // Move this item to the right position if its name changed
+         document.getElementById("nicklist").removeChild(item);
+         this.addNick(item);
+       ]]>
+       </body>
+     </method>
+     <method name="removeBuddy">
+       <parameter name="aName"/>
+       <body>
+       <![CDATA[
+         if (!this._hasBuddy(aName))
+           throw "Cannot remove a buddy that was not in the room";
+         var item = this.buddies[aName];
+         item.parentNode.removeChild(item);
+         delete this.buddies[aName];
+       ]]>
+       </body>
+     </method>
+
+     <method name="updateTopic">
+       <body>
+       <![CDATA[
+          let cti = document.getElementById("conv-top-info");
+          let statusMessage = this.getElt("statusMessage");
+          if (this._conv.topicSettable)
+            cti.setAttribute("topicEditable", "true");
+          else
+            cti.removeAttribute("topicEditable");
+
+          var topic = this._conv.topic;
+          if (topic) {
+            cti.setAttribute("statusTooltiptext", topic);
+            cti.removeAttribute("noTopic");
+          }
+          else {
+            cti.removeAttribute("statusTooltiptext");
+            cti.setAttribute("noTopic", "true");
+            topic = this.bundle.GetStringFromName("noTopic");
+          }
+          cti.setAttribute("statusMessage", topic);
+          cti.setAttribute("statusMessageWithDash", " - " + topic);
+          cti.removeAttribute("userIcon");
+       ]]>
+       </body>
+     </method>
+
+     <method name="focus">
+       <body>
+       <![CDATA[
+         this.editor.focus();
+         this.displayStatusText();
+         if (!this.loaded)
+           return;
+
+         if (this.tab) {
+           this.tab.removeAttribute("unread");
+           this.tab.removeAttribute("attention");
+         }
+         this._conv.markAsRead();
+       ]]>
+       </body>
+     </method>
+
+     <method name="displayStatusText">
+       <body>
+       <![CDATA[
+         if (this.tab && !this.tab.selected)
+           return;
+
+         if ("XULBrowserWindow" in window) {
+           window.XULBrowserWindow.setStatus(this._statusText);
+           if ("setStatusEnd" in window.XULBrowserWindow)
+             window.XULBrowserWindow.setStatusEnd(this._statusTextEnd,
+                                                  this._statusTextEndIsError);
+         }
+       ]]>
+       </body>
+     </method>
+
+     <method name="updateTyping">
+       <body>
+       <![CDATA[
+          let typingState = this._conv.typingState;
+          let cti = document.getElementById("conv-top-info");
+          cti.removeAttribute("typing");
+          cti.removeAttribute("typed");
+
+          let name = this._conv.title;//.replace(/^([a-zA-Z0-9.]+)[@\s].*/, "$1");
+          if (typingState == Ci.prplIConvIM.TYPING) {
+            cti.setAttribute("typing", "true");
+            cti.setAttribute("statusTypeTooltiptext",
+                             this.bundle.formatStringFromName("chat.contactIsTyping",
+                                                              [name], 1));
+            cti.setAttribute("statusMessage",
+                             this.bundle.GetStringFromName("chat.isTyping"));
+          }
+          else if (typingState == Ci.prplIConvIM.TYPED) {
+            cti.setAttribute("typed", "true");
+            cti.setAttribute("statusTypeTooltiptext",
+                             this.bundle.formatStringFromName("chat.contactHasStoppedTyping",
+                                                              [name], 1));
+            cti.setAttribute("statusMessage",
+                             this.bundle.GetStringFromName("chat.hasStoppedTyping"));
+          }
+       ]]>
+       </body>
+     </method>
+
+     <method name="getElt">
+       <parameter name="aAnonId"/>
+       <body>
+       <![CDATA[
+         return document.getAnonymousElementByAttribute(this, "anonid", aAnonId);
+       ]]>
+       </body>
+     </method>
+
+     <method name="updateConvStatus">
+       <body>
+       <![CDATA[
+          let cti = document.getElementById("conv-top-info");
+          cti.setAttribute("prplIcon",
+                           this._conv.account.protocol.iconBaseURI + "icon.png");
+
+          if (this._conv.isChat) {
+            this.updateTopic();
+            cti.setAttribute("status", "chat");
+            cti.setAttribute("displayName", this._conv.title);
+          }
+          else {
+            let displayName = this._conv.title;
+            let statusText = "";
+            let statusType = Ci.imIStatusInfo.STATUS_UNKNOWN;
+
+            let buddy = this._conv.buddy;
+            if (!buddy || !buddy.account.connected) {
+              this._statusText = "";
+              this.displayStatusText();
+              cti.removeAttribute("userIcon");
+            }
+            else {
+              displayName = buddy.displayName;
+              statusText = buddy.statusText;
+              statusType = buddy.statusType;
+              cti.setAttribute("userIcon", buddy.buddyIconFilename);
+            }
+
+            cti.setAttribute("displayName", displayName);
+            if (statusText)
+              statusText = " - " + statusText;
+            cti.setAttribute("statusMessageWithDash", statusText);
+            let statusString = Status.toLabel(statusType);
+            cti.setAttribute("statusMessage", statusString + statusText);
+            cti.setAttribute("status", Status.toAttribute(statusType));
+            cti.setAttribute("statusTypeTooltiptext", statusString);
+            this.updateTyping();
+          }
+       ]]>
+       </body>
+     </method>
+
+     <method name="showParticipants">
+       <body>
+       <![CDATA[
+         if (this._conv.isChat) {
+           let nicklist = document.getElementById("nicklist");
+           while (nicklist.firstChild)
+             nicklist.removeChild(nicklist.firstChild);
+           // Populate the nicklist
+           this.buddies = {};
+           let nicks = fixIterator(this.conv.getParticipants());
+           for (let n in nicks)
+             this.addBuddy(n);
+           this.updateParticipantCount();
+         }
+       ]]>
+       </body>
+     </method>
+
+     <method name="initConversationUI">
+       <body>
+       <![CDATA[
+         if (this._conv.isChat) {
+           this.updateTopic();
+           this.setAttribute("chat", "true");
+           let cti = document.getElementById("conv-top-info");
+           cti.setAttribute("displayName", this._conv.title);
+           cti.setAttribute("status", "chat");
+
+           this.showParticipants();
+         }
+
+         if (this.tab)
+           this.tab.setAttribute("label", this._conv.title);
+
+         if (!("Status" in window))
+           Components.utils.import("resource:///modules/imStatusUtils.jsm");
+         this.updateConvStatus();
+         this.initTextboxFormat();
+       ]]>
+       </body>
+     </method>
+
+     <!-- nsIObserver implementation -->
+     <method name="observe">
+       <parameter name="aSubject"/>
+       <parameter name="aTopic"/>
+       <parameter name="aData"/>
+       <body>
+       <![CDATA[
+         if (aTopic == "conversation-loaded") {
+           if (aSubject != this.browser)
+             return;
+
+           this.browser.progressBar = this.getElt("browserProgress");
+
+           // Display all queued messages. Use a timeout so that message text
+           // modifiers can be added with observers for this notification.
+           if (!this.loaded)
+             setTimeout(this._showFirstMessages.bind(this), 0);
+
+           Services.obs.removeObserver(this, "conversation-loaded");
+           return;
+         }
+
+         if (aTopic.indexOf("chat-buddy-") == 0 && !this.tab.selected)
+           return;
+
+         switch(aTopic) {
+         case "new-text":
+           if (this.loaded)
+             this.addMsg(aSubject);
+           break;
+
+         case "update-typing":
+           if (this.tab && this.tab.selected)
+             this.updateTyping();
+           break;
+
+         case "status-text-changed":
+           this._statusText = aData;
+           this.displayStatusText();
+           break;
+
+         case "replying-to-prompt":
+           this.addPrompt(aData);
+           break;
+
+         case "target-purple-conversation-changed":
+         case "update-conv-title":
+           if (this.tab)
+               this.tab.setAttribute("label", this.conv.title);
+           // Update the status too.
+         case "update-buddy-status":
+         case "update-buddy-icon":
+         case "update-conv-chatleft":
+           if (this.tab && this.tab.selected)
+             this.updateConvStatus();
+           break;
+
+         case "chat-buddy-add":
+           aSubject.QueryInterface(Ci.nsISimpleEnumerator);
+           while (aSubject.hasMoreElements())
+             this.addBuddy(aSubject.getNext());
+           this.updateParticipantCount();
+           break;
+
+         case "chat-buddy-remove":
+           aSubject.QueryInterface(Ci.nsISimpleEnumerator);
+           while (aSubject.hasMoreElements()) {
+             let nick = aSubject.getNext();
+             nick.QueryInterface(Ci.nsISupportsString);
+             this.removeBuddy(nick.toString());
+           }
+           this.updateParticipantCount();
+           break;
+
+         case "chat-buddy-update":
+           this.updateBuddy(aSubject, aData);
+           break;
+         case "chat-update-topic":
+           this.updateTopic();
+           break;
+         }
+       ]]>
+       </body>
+     </method>
+
+     <method name="onNicklistKeyPress">
+      <parameter name="event"/>
+      <body>
+      <![CDATA[
+        if (event.keyCode != event.DOM_VK_RETURN &&
+            event.keyCode != event.DOM_VK_ENTER)
+          return;
+
+        let listbox = event.originalTarget;
+        if (listbox.selectedCount == 0)
+          return;
+
+        for (let i = 0; i < listbox.selectedCount; ++i) {
+          let nick = listbox.getSelectedItem(i).chatBuddy.name;
+          let name = this._conv.target.getNormalizedChatBuddyName(nick);
+          this._conv.account.createConversation(name);
+        }
+      ]]>
+      </body>
+     </method>
+
+     <method name="onNickClick">
+      <parameter name="event"/>
+      <body>
+      <![CDATA[
+        // Open a private conversation in new tab on a middle or double click.
+        if (event.button == 1 || (event.button == 0 && event.detail == 2)) {
+          var nick = event.originalTarget.chatBuddy.name;
+          let name = this._conv.target.getNormalizedChatBuddyName(nick);
+          let newConv = this._conv.account.createConversation(name);
+          Conversations.focusConversation(newConv);
+        }
+      ]]>
+      </body>
+     </method>
+
+     <property name="convId">
+       <getter>
+         <![CDATA[
+           return this._conv.id;
+         ]]>
+       </getter>
+     </property>
+
+     <property name="conv">
+       <getter>
+         <![CDATA[
+           return this._conv;
+         ]]>
+       </getter>
+       <setter>
+         <![CDATA[
+           if (this._conv && val)
+             throw("Already initialized");
+           if (!val) {
+             // this conversation has probably been moved to another
+             // tab. Forget the purpleConversation so that it isn't
+             // closed when destroying this binding.
+             this._forgetConv();
+             return val;
+           }
+           this._conv = val;
+           this._conv.addObserver(this);
+           this.browser.init(this._conv);
+           this.initConversationUI();
+           return val;
+         ]]>
+       </setter>
+     </property>
+
+     <field name="_editor">null</field>
+     <property name="editor">
+       <getter>
+         <![CDATA[
+          if (!this._editor)
+            this._editor = this.getElt("inputBox");
+          return this._editor;
+         ]]>
+       </getter>
+     </property>
+
+     <property name="browser" onget="return this.getElt('browser');"/>
+     <property name="contentWindow" onget="return this.browser.contentWindow;"/>
+     <property name="findbar" onget="return this.getElt('FindToolbar');"/>
+
+     <property name="bundle">
+       <getter>
+         <![CDATA[
+          if (!this._bundle) {
+            this._bundle =
+              Services.strings.createBundle("chrome://messenger/locale/chat.properties");
+          }
+          return this._bundle;
+         ]]>
+       </getter>
+     </property>
+    </implementation>
+  </binding>
+
+  <binding id="conv-info-large"
+           extends="chrome://global/content/bindings/toolbar.xml#toolbar">
+    <resources>
+      <stylesheet src="chrome://messenger/content/chat/chat.css"/>
+    </resources>
+    <content>
+      <xul:stack anonid="statusImageStack" class="statusImageStack">
+        <xul:image anonid="userIcon" class="userIcon" mousethrough="always"
+                   xbl:inherits="src=userIcon"/>
+        <xul:image anonid="statusTypeIcon" class="statusTypeIcon"
+                   xbl:inherits="status,typing,typed,tooltiptext=statusTypeTooltiptext"/>
+      </xul:stack>
+      <xul:stack class="displayNameAndstatusMessageStack"
+                 mousethrough="always" flex="1">
+        <xul:hbox align="center" flex="1">
+          <xul:description anonid="displayName" class="displayName" flex="1"
+                           crop="end" xbl:inherits="value=displayName"/>
+          <xul:image class="prplIcon" anonid="targetPrplIcon"
+                     xbl:inherits="src=prplIcon"/>
+        </xul:hbox>
+        <xul:description anonid="statusMessage" class="statusMessage"
+                         xbl:inherits="value=statusMessage,tooltiptext=statusTooltiptext,editable=topicEditable,editing,noTopic"
+                         mousethrough="never" crop="end" flex="100000"/>
+      </xul:stack>
+    </content>
+    <implementation>
+     <constructor>
+      <![CDATA[
+        this.topic
+            .addEventListener("click", this.startEditTopic.bind(this));
+        // Cancel any ongoing edit if the binding changes.
+        this.removeAttribute("editing");
+      ]]>
+     </constructor>
+
+     <property name="topic">
+       <getter>
+         <![CDATA[
+           return document.getAnonymousElementByAttribute(this, "anonid",
+                                                          "statusMessage");
+         ]]>
+       </getter>
+     </property>
+
+     <method name="finishEditTopic">
+       <parameter name="aSave"/>
+       <body>
+       <![CDATA[
+         if (!this.hasAttribute("editing"))
+           return;
+
+         let elt = this.topic;
+         if (aSave) {
+           // apply the new topic only if it is different from the current one
+           if (elt.value != elt.getAttribute("value"))
+             document.getBindingParent(this)._conv.topic = elt.value;
+         }
+         this.removeAttribute("editing");
+         elt.removeEventListener("keypress", this._topicKeyPress, true);
+         delete this._topicKeyPress;
+         elt.removeEventListener("blur", this._topicBlur, false);
+         delete this._topicBlur;
+       ]]>
+       </body>
+     </method>
+
+     <method name="topicKeyPress">
+       <parameter name="aEvent"/>
+       <body>
+       <![CDATA[
+         switch (aEvent.keyCode) {
+           case aEvent.DOM_VK_RETURN:
+           case aEvent.DOM_VK_ENTER:
+             this.finishEditTopic(true);
+             break;
+
+           case aEvent.DOM_VK_ESCAPE:
+             this.finishEditTopic(false);
+             aEvent.stopPropagation();
+             aEvent.preventDefault();
+             break;
+         }
+       ]]>
+       </body>
+     </method>
+
+     <method name="topicBlur">
+       <parameter name="aEvent"/>
+       <body>
+       <![CDATA[
+         if (aEvent.originalTarget == this.topic.inputField)
+           this.finishEditTopic(true);
+       ]]>
+       </body>
+     </method>
+
+     <method name="startEditTopic">
+       <body>
+       <![CDATA[
+          let elt = this.topic;
+          if (!elt.hasAttribute("editable") || this.hasAttribute("editing"))
+            return;
+
+          this.setAttribute("editing", "true");
+          this._topicKeyPress = this.topicKeyPress.bind(this);
+          elt.addEventListener("keypress", this._topicKeyPress);
+          this._topicBlur = this.topicBlur.bind(this);
+          elt.addEventListener("blur", this._topicBlur);
+          // force binding attachmant by forcing layout
+          elt.getBoundingClientRect();
+          if (this.hasAttribute("noTopic"))
+            elt.value = "";
+          elt.select();
+       ]]>
+       </body>
+     </method>
+    </implementation>
+  </binding>
+</bindings>
new file mode 100644
--- /dev/null
+++ b/mail/components/im/content/imgroup.xml
@@ -0,0 +1,242 @@
+<?xml version="1.0"?>
+<!-- ***** BEGIN LICENSE BLOCK *****
+   - Version: MPL 1.1/GPL 2.0/LGPL 2.1
+   -
+   - The contents of this file are subject to the Mozilla Public License Version
+   - 1.1 (the "License"); you may not use this file except in compliance with
+   - the License. You may obtain a copy of the License at
+   - http://www.mozilla.org/MPL/
+   -
+   - Software distributed under the License is distributed on an "AS IS" basis,
+   - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+   - for the specific language governing rights and limitations under the
+   - License.
+   -
+   - The Original Code is the Instantbird messenging client, released
+   - 2007.
+   -
+   - The Initial Developer of the Original Code is
+   - Florian QUEZE <florian@instantbird.org>.
+   - Portions created by the Initial Developer are Copyright (C) 2007
+   - the Initial Developer. All Rights Reserved.
+   -
+   - Contributor(s):
+   -
+   - Alternatively, the contents of this file may be used under the terms of
+   - either the GNU General Public License Version 2 or later (the "GPL"), or
+   - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+   - in which case the provisions of the GPL or the LGPL are applicable instead
+   - of those above. If you wish to allow use of your version of this file only
+   - under the terms of either the GPL or the LGPL, and not to allow others to
+   - use your version of this file under the terms of the MPL, indicate your
+   - decision by deleting the provisions above and replace them with the notice
+   - and other provisions required by the GPL or the LGPL. If you do not delete
+   - the provisions above, a recipient may use your version of this file under
+   - the terms of any one of the MPL, the GPL or the LGPL.
+   -
+   - ***** END LICENSE BLOCK ***** -->
+
+<!DOCTYPE bindings>
+
+<bindings id="groupBindings"
+          xmlns="http://www.mozilla.org/xbl"
+          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+          xmlns:xbl="http://www.mozilla.org/xbl">
+
+  <binding id="group" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+    <content persist="closed" collapsed="true">
+      <xul:image class="twisty"/>
+      <xul:label flex="1" crop="end" xbl:inherits="value=name"/>
+    </content>
+    <implementation>
+     <!-- Takes as input two contact elements (imIContact type) and compares
+          - their nicknames alphabetically (case insensitive). This method
+          - behaves as a callback that Array.sort accepts as a parameter.
+          -->
+     <method name="sortComparator">
+      <parameter name="aContactA"/>
+      <parameter name="aContactB"/>
+      <body>
+      <![CDATA[
+        if (aContactA.statusType != aContactB.statusType)
+          return aContactB.statusType - aContactA.statusType;
+        let a = aContactA.displayName.toLowerCase();
+        let b = aContactB.displayName.toLowerCase();
+        return a.localeCompare(b);
+      ]]>
+      </body>
+     </method>
+
+     <field name="contacts">[]</field>
+     <field name="contactsById">({})</field>
+
+     <method name="addContact">
+      <parameter name="aContact"/>
+      <parameter name="aTagName"/>
+      <body>
+      <![CDATA[
+        if (this.contactsById.hasOwnProperty(aContact.id))
+          return;
+
+        let contactElt = document.createElement(aTagName || "imcontact");
+        if (this.hasAttribute("closed"))
+          contactElt.setAttribute("collapsed", "true");
+
+        let end = this.contacts.length;
+        // Avoid the binary search loop if the contacts were already sorted.
+        if (end != 0 &&
+            this.sortComparator(aContact, this.contacts[end - 1].contact) < 0) {
+          let start = 0;
+          while (start < end) {
+            let middle = start + Math.floor((end - start) / 2);
+            if (this.sortComparator(aContact, this.contacts[middle].contact) < 0)
+              end = middle;
+            else
+              start = middle + 1;
+          }
+        }
+        let last = end == 0 ? this : this.contacts[end - 1];
+        this.parentNode.insertBefore(contactElt, last.nextSibling);
+        contactElt.build(aContact);//, this);
+        contactElt.group = this;
+        this.contacts.splice(end, 0, contactElt);
+        this.contactsById[aContact.id] = contactElt;
+        this.removeAttribute("collapsed");
+        this._updateGroupLabel();
+        return contactElt;
+      ]]>
+      </body>
+     </method>
+
+     <method name="updateContactPosition">
+      <parameter name="aSubject"/>
+      <body>
+      <![CDATA[
+        let contactElt = this.contactsById[aSubject.id];
+        let index = this.contacts.indexOf(contactElt);
+        if (index == -1) {
+          // Sometimes we get a display-name-changed notification for
+          // an offline contact, if it's not in the list, just ignore it.
+          return;
+        }
+        // See if the position of the contact should be changed.
+        if (index != 0 &&
+            this.sortComparator(contactElt.contact, this.contacts[index - 1].contact) < 0 ||
+            index != this.contacts.length - 1 &&
+            this.sortComparator(contactElt.contact, this.contacts[index + 1].contact) > 0) {
+          let oldItem = this.removeContact(aSubject);
+          let newItem = this.addContact(aSubject);
+          let list = this.parentNode;
+          if (list.selectedItem == oldItem)
+            list.selectedItem = newItem;
+        }
+      ]]>
+      </body>
+     </method>
+
+     <method name="removeContact">
+      <parameter name="aContact"/>
+      <body>
+      <![CDATA[
+        let contact = this.contactsById[aContact.id];
+        if (!contact)
+          throw "Removing a contact that isn't here?";
+
+        // create a new array to remove without breaking for each loops.
+        this.contacts = this.contacts.filter(function(c) c !== contact);
+        delete this.contactsById[contact.contact.id];
+
+        contact.destroy();
+
+        // Check if some contacts remain in the group, if empty hide it.
+        if (!this.contacts.length)
+          this.setAttribute("collapsed", "true");
+        else
+          this._updateGroupLabel();
+
+        return contact;
+      ]]>
+      </body>
+     </method>
+
+     <method name="_updateClosedState">
+      <parameter name="aClosed"/>
+      <body>
+      <![CDATA[
+        for each (let contact in this.contacts)
+          contact.collapsed = aClosed;
+      ]]>
+      </body>
+     </method>
+
+     <method name="close">
+      <body>
+      <![CDATA[
+        if (this.hasAttribute("closed")) {
+          this.removeAttribute("closed");
+          this._updateClosedState(false);
+        }
+        else {
+          this.setAttribute("closed", "true");
+          this._updateClosedState(true);
+        }
+
+        this._updateGroupLabel();
+      ]]>
+      </body>
+     </method>
+
+     <field name="displayName"></field>
+     <method name="_updateGroupLabel">
+      <body>
+      <![CDATA[
+        if (!this.displayName)
+          this.displayName = this.getAttribute("name");
+        let name = this.displayName;
+        if (this.hasAttribute("closed"))
+          name += " (" + this.contacts.length + ")";
+
+        this.setAttribute("name", name);
+      ]]>
+      </body>
+     </method>
+
+     <method name="keyPress">
+      <parameter name="aEvent"/>
+      <body>
+      <![CDATA[
+        switch (aEvent.keyCode) {
+          case aEvent.DOM_VK_RETURN:
+          case aEvent.DOM_VK_ENTER:
+            this.close();
+            break;
+
+          case aEvent.DOM_VK_LEFT:
+            if (!this.hasAttribute("closed"))
+              this.close();
+            break;
+
+          case aEvent.DOM_VK_RIGHT:
+            if (this.hasAttribute("closed"))
+              this.close();
+            break;
+        }
+      ]]>
+      </body>
+     </method>
+    </implementation>
+    <handlers>
+     <handler event="click">
+     <![CDATA[
+        // Check if there was 1 click on the image or 2 clicks on the label
+        if ((event.detail == 1 && event.originalTarget.localName == "image") ||
+            (event.detail == 2 && event.originalTarget.localName == "label"))
+          this.close();
+
+        if (event.originalTarget.localName == "button")
+          this.hide();
+     ]]>
+     </handler>
+    </handlers>
+  </binding>
+</bindings>
new file mode 100644
--- /dev/null
+++ b/mail/components/im/content/imsearch.xml
@@ -0,0 +1,258 @@
+<?xml version="1.0"?>
+
+<!-- ***** BEGIN LICENSE BLOCK *****
+  - Version: MPL 1.1/GPL 2.0/LGPL 2.1
+  -
+  - The contents of this file are subject to the Mozilla Public License Version
+  - 1.1 (the "License"); you may not use this file except in compliance with
+  - the License. You may obtain a copy of the License at
+  - http://www.mozilla.org/MPL/
+  -
+  - Software distributed under the License is distributed on an "AS IS" basis,
+  - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+  - for the specific language governing rights and limitations under the
+  - License.
+  -
+  - The Original Code is Mozilla Communicator client code, released
+  - March 31, 1998.
+  -
+  - The Initial Developer of the Original Code is
+  - Netscape Communications Corporation.
+  - Portions created by the Initial Developer are Copyright (C) 1998-1999
+  - the Initial Developer. All Rights Reserved.
+  -
+  - Contributor(s):
+  -   Scott MacGregor <mscott@mozilla.org>
+  -   Andrew Sutherland <asutherland@asutherland.org>
+  -   David Ascher <dascher@mozillamessaging.com>
+  -   Thomas Düllmann <bugzilla2009@duellmann24.net>
+  -
+  - Alternatively, the contents of this file may be used under the terms of
+  - either of the GNU General Public License Version 2 or later (the "GPL"),
+  - or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+  - in which case the provisions of the GPL or the LGPL are applicable instead
+  - of those above. If you wish to allow use of your version of this file only
+  - under the terms of either the GPL or the LGPL, and not to allow others to
+  - use your version of this file under the terms of the MPL, indicate your
+  - decision by deleting the provisions above and replace them with the notice
+  - and other provisions required by the GPL or the LGPL. If you do not delete
+  - the provisions above, a recipient may use your version of this file under
+  - the terms of any one of the MPL, the GPL or the LGPL.
+  - ***** END LICENSE BLOCK ***** -->
+
+<!DOCTYPE bindings [
+<!ENTITY % messengerDTD SYSTEM "chrome://messenger/locale/messenger.dtd">
+%messengerDTD;
+]>
+
+<bindings id="SearchBindings"
+   xmlns="http://www.mozilla.org/xbl"
+   xmlns:html="http://www.w3.org/1999/xhtml"
+   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+   xmlns:xbl="http://www.mozilla.org/xbl">
+
+  <!--
+    - The glodaSearch binding implements a gloda-backed search mechanism.  The
+    -  actual search logic comes from the glodaFacet tab mode in the
+    -  glodaFacetTabType definition.  This binding serves as a means to display
+    -  and alter the current search query if a "glodaFacet" tab is displayed,
+    -  or enter a search query and spawn a new "glodaFacet" tab if one is
+    -  currently not displayed.
+    -
+    - This widget used to have many weird implementation nuances.  Now we are
+    -  just a little bit of extra stuff on top of the toolkit autocomplete
+    -  implementation.  Our deviations are:
+    -  - We collapse ourselves when gloda is disabled; we track the state.
+    -  -
+    -->
+  <binding id="IMGlodaSearch"
+           extends="chrome://global/content/bindings/autocomplete.xml#autocomplete">
+    <resources>
+      <stylesheet src="chrome://messenger/skin/searchBox.css"/>
+    </resources>
+
+    <handlers>
+      <handler event="drop" phase="capturing"><![CDATA[
+        nsDragAndDrop.drop(event, this.searchInputDNDObserver);
+      ]]></handler>
+
+      <handler event="keypress" keycode="VK_RETURN"><![CDATA[
+        this.doSearch();
+        event.preventDefault();
+        event.stopPropagation();
+      ]]></handler>
+      <handler event="keypress" keycode="VK_ESCAPE"><![CDATA[
+        this.clearSearch();
+        event.preventDefault();
+        event.stopPropagation();
+      ]]></handler>
+    </handlers>
+
+    <implementation implements="nsIObserver">
+      <constructor><![CDATA[
+        const Cc = Components.classes;
+        const Ci = Components.interfaces;
+        const Cu = Components.utils;
+        Cu.import("resource:///modules/errUtils.js");
+        try {
+          this.setAttribute(
+            "placeholder",
+            this.getAttribute("emptytextbase")
+                 .replace("#1", this.getAttribute(
+                                  Application.platformIsMac ?
+                                  "keyLabelMac" : "keyLabelNonMac")));
+
+          var prefBranch =
+              Components.classes['@mozilla.org/preferences-service;1'].
+              getService(Components.interfaces.nsIPrefBranch2);
+
+          if (typeof gSearchInputObserversRegistered != "undefined") {
+            // due to a bug in XBL that means that destructors don't get
+            // called reliably, the customize toolbar path will end up creating
+            // two clones of this widget, and never destroy them.  So the destructor
+            // isn't reliably called, and so we must use a global to avoid
+            // registering ourselves for the same event multiple times (twice
+            // per invocation of the customize-toolbar).
+
+            // We need to test for undefined above because if the widget is
+            // created from the customize toolbar, its namespace won't include
+            // a definition of gSearchInputObserversRegistered -- and we don't
+            // care, since we don't want to register observers in that scope.
+
+            prefBranch.addObserver("mailnews.database.global.indexer.enabled",
+                                   this._prefObserver, false);
+          }
+
+          this.glodaEnabled =
+            prefBranch.getBoolPref("mailnews.database.global.indexer.enabled");
+          this.collapsed = !this.glodaEnabled;
+
+          // make sure we set our emptytext here from the get-go
+        if (this.hasAttribute("placeholder"))
+          this.placeholder = this.getAttribute("placeholder");
+        } catch (e) {
+          logException(e, true);
+        }
+      ]]></constructor>
+
+      <destructor>
+        <![CDATA[
+          var prefBranch =
+              Components.classes['@mozilla.org/preferences-service;1'].
+              getService(Components.interfaces.nsIPrefBranch);
+          prefBranch.removeObserver("mailnews.database.global.indexer.enabled",
+                                    this._prefObserver);
+        ]]>
+      </destructor>
+
+      <field name="_prefObserver">({
+        inputSearch: this,
+        observe: function(subject, topic, data)
+        {
+          if (topic == "nsPref:changed") {
+            subject.QueryInterface(Components.interfaces.nsIPrefBranch);
+            switch (data) {
+            case "mailnews.database.global.indexer.enabled":
+              this.inputSearch.glodaEnabled =
+                gPrefBranch.getBoolPref(
+                  "mailnews.database.global.indexer.enabled");
+              this.inputSearch.collapsed = !this.inputSearch.glodaEnabled;
+              break;
+            }
+          }
+        },
+
+        QueryInterface: function(aIID)
+        {
+          if (aIID.equals(Components.interfaces.nsIObserver) ||
+              aIID.equals(Components.interfaces.nsISupports))
+            return this;
+          throw Components.results.NS_NOINTERFACE;
+        }
+        });
+      </field>
+      <property name="menupopup" readonly="true">
+        <getter><![CDATA[
+          return document.getAnonymousElementByAttribute(
+                   this, 'anonid', 'quick-search-menupopup');
+        ]]></getter>
+      </property>
+
+      <property name="state">
+        <getter><![CDATA[
+          return {
+            'string': this.value
+          };
+        ]]></getter>
+        <setter><![CDATA[
+          this.value = val['string'];
+        ]]></setter>
+      </property>
+
+      // DND Observer
+      <field name="searchInputDNDObserver" readonly="true"><![CDATA[
+      ({
+        inputSearch: this,
+
+        onDrop: function (aEvent, aXferData, aDragSession) {
+          try {
+            if (aXferData.data) {
+              this.inputSearch.focus();
+              this.inputSearch.value = aXferData.data;
+              // XXX for some reason the input field is _cleared_ even though
+              // the search works.
+              this.inputSearch.doSearch();
+            }
+          } catch (e) {
+            logException(e);
+          }
+        },
+
+        getSupportedFlavours: function () {
+          var flavourSet = new FlavourSet();
+          flavourSet.appendFlavour("text/unicode");
+          return flavourSet;
+        }
+      })
+      ]]></field>
+
+      <method name="doSearch">
+        <body><![CDATA[
+          try {
+            if (this.value) {
+              let tabmail = document.getElementById("tabmail");
+              // If the current tab is a gloda search tab, reset the value
+              //  to the initial search value.  Otherwise, clear it.  This
+              //  is the value that 3is going to be saved with the current
+              //  tab when we switch back to it next.
+              let searchString = this.value;
+
+              if (tabmail.currentTabInfo.mode.name == "glodaFacet") {
+                // we'd rather reuse the existing tab (and somehow do something
+                // smart with any preexisting facet choices, but that's a
+                // bit hard right now, so doing the cheap thing and closing
+                // this tab and starting over
+                tabmail.closeTab();
+              }
+              this.value = ''; // clear our value, to avoid persistence
+              if (!("GlodaIMSearcher" in window))
+                Cu.import("resource:///modules/search_im.js");
+              tabmail.openTab("glodaFacet", {
+                searcher: new GlodaIMSearcher(null, searchString)
+              });
+            }
+          } catch (e) {
+            logException(e);
+          }
+        ]]>
+        </body>
+      </method>
+      <method name="clearSearch">
+        <body><![CDATA[
+          this.value = "";
+        ]]></body>
+      </method>
+    </implementation>
+  </binding>
+
+</bindings>
new file mode 100644
--- /dev/null
+++ b/mail/components/im/content/joinchat.js
@@ -0,0 +1,182 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2007.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+Components.utils.import("resource:///modules/iteratorUtils.jsm");
+Components.utils.import("resource:///modules/imServices.jsm");
+
+const Ci = Components.interfaces;
+const autoJoinPref = "autoJoin";
+
+var joinChat = {
+  onload: function jc_onload() {
+    var accountList = document.getElementById("accountlist");
+    for (let acc in fixIterator(Services.accounts.getAccounts())) {
+      if (!acc.connected || !acc.canJoinChat)
+        continue;
+      var proto = acc.protocol;
+      var item = accountList.appendItem(acc.name, acc.id, proto.name);
+      item.setAttribute("image", proto.iconBaseURI + "icon.png");
+      item.setAttribute("class", "menuitem-iconic");
+      item.account = acc;
+    }
+    if (!accountList.itemCount) {
+      document.getElementById("joinChatDialog").cancelDialog();
+      throw "No connected MUC enabled account!";
+    }
+    accountList.selectedIndex = 0;
+  },
+
+  onAccountSelect: function jc_onAccountSelect() {
+    let ab = document.getElementById("separatorRow1");
+    while (ab.nextSibling && ab.nextSibling.id != "separatorRow2")
+      ab.parentNode.removeChild(ab.nextSibling);
+
+    let acc = document.getElementById("accountlist").selectedItem.account;
+    let sep = document.getElementById("separatorRow2");
+    let defaultValues = acc.getChatRoomDefaultFieldValues();
+    joinChat._values = defaultValues;
+    joinChat._fields = [];
+    joinChat._account = acc;
+
+    let protoId = acc.protocol.id;
+    document.getElementById("autojoin").hidden =
+      !(protoId == "prpl-irc" || protoId == "prpl-jabber" ||
+      protoId == "prpl-gtalk");
+
+    for (let field in fixIterator(acc.getChatRoomFields())) {
+      let row = document.createElement("row");
+
+      let label = document.createElement("label");
+      let text = field.label;
+      let match = /_(.)/.exec(text);
+      if (match) {
+        label.setAttribute("accesskey", match[1]);
+        text = text.replace(/_/, "");
+      }
+      label.setAttribute("value", text);
+      label.setAttribute("control", "field-" + field.identifier);
+      row.appendChild(label);
+
+      let textbox = document.createElement("textbox");
+      textbox.setAttribute("id", "field-" + field.identifier);
+      let val = defaultValues.getValue(field.identifier);
+      if (val)
+        textbox.setAttribute("value", val);
+      if (field.type == Ci.prplIChatRoomField.TYPE_PASSWORD)
+        textbox.setAttribute("type", "password");
+      else if (field.type == Ci.prplIChatRoomField.TYPE_INT) {
+        textbox.setAttribute("type", "number");
+        textbox.setAttribute("min", field.min);
+        textbox.setAttribute("max", field.max);
+      }
+      row.appendChild(textbox);
+
+      if (!field.required) {
+        label = document.createElement("label");
+        text = document.getElementById("optionalcolumn")
+                       .getAttribute("labeltxt");
+        label.setAttribute("value", text);
+        row.appendChild(label);
+      }
+
+      row.setAttribute("align", "baseline");
+      sep.parentNode.insertBefore(row, sep);
+      joinChat._fields.push({field: field, textbox: textbox});
+    }
+
+    window.sizeToContent();
+  },
+
+  join: function jc_join() {
+    let values = joinChat._values;
+    for each (let field in joinChat._fields) {
+      let val = field.textbox.value;
+      if (!val && field.field.required) {
+        field.textbox.focus();
+        //FIXME: why isn't the return false enough?
+        throw "Some required fields are empty!";
+        return false;
+      }
+      if (val)
+        values.setValue(field.field.identifier, val);
+    }
+    let account = joinChat._account;
+    account.joinChat(values);
+
+    let protoId = account.protocol.id;
+    if (protoId != "prpl-irc" && protoId != "prpl-jabber" &&
+        protoId != "prpl-gtalk")
+      return true;
+
+    let name;
+    if (protoId == "prpl-irc")
+      name = values.getValue("channel");
+    else
+      name = values.getValue("room") + "@" + values.getValue("server");
+
+/*
+    let conv = Services.conversations.getConversationByNameAndAccount(name,
+                                                                      account,
+                                                                      true);
+    if (conv)
+      // TODO: select conv
+*/
+
+    if (document.getElementById("autojoin").checked) {
+      if (protoId == "prpl-gtalk")
+        name += "/" + values.getValue("nick");
+      else if (protoId != "prpl-irc")
+        name += "/" + values.getValue("handle");
+
+      let prefBranch =
+        Services.prefs.getBranch("messenger.account." + account.id + ".");
+      let autojoin = [ ];
+      if (prefBranch.prefHasUserValue(autoJoinPref)) {
+        let prefValue = prefBranch.getCharPref(autoJoinPref);
+        if (prefValue)
+          autojoin = prefValue.split(",");
+      }
+
+      if (autojoin.indexOf(name) == -1) {
+        autojoin.push(name);
+        prefBranch.setCharPref(autoJoinPref, autojoin.join(","));
+      }
+    }
+
+    return true;
+  }
+};
new file mode 100644
--- /dev/null
+++ b/mail/components/im/content/joinchat.xul
@@ -0,0 +1,81 @@
+<?xml version="1.0"?>
+<!-- ***** BEGIN LICENSE BLOCK *****
+   - Version: MPL 1.1/GPL 2.0/LGPL 2.1
+   -
+   - The contents of this file are subject to the Mozilla Public License Version
+   - 1.1 (the "License"); you may not use this file except in compliance with
+   - the License. You may obtain a copy of the License at
+   - http://www.mozilla.org/MPL/
+   -
+   - Software distributed under the License is distributed on an "AS IS" basis,
+   - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+   - for the specific language governing rights and limitations under the
+   - License.
+   -
+   - The Original Code is the Instantbird messenging client, released
+   - 2007.
+   -
+   - The Initial Developer of the Original Code is
+   - Florian QUEZE <florian@instantbird.org>.
+   - Portions created by the Initial Developer are Copyright (C) 2008
+   - the Initial Developer. All Rights Reserved.
+   -
+   - Contributor(s):
+   -
+   - Alternatively, the contents of this file may be used under the terms of
+   - either the GNU General Public License Version 2 or later (the "GPL"), or
+   - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+   - in which case the provisions of the GPL or the LGPL are applicable instead
+   - of those above. If you wish to allow use of your version of this file only
+   - under the terms of either the GPL or the LGPL, and not to allow others to
+   - use your version of this file under the terms of the MPL, indicate your
+   - decision by deleting the provisions above and replace them with the notice
+   - and other provisions required by the GPL or the LGPL. If you do not delete
+   - the provisions above, a recipient may use your version of this file under
+   - the terms of any one of the MPL, the GPL or the LGPL.
+   -
+   - ***** END LICENSE BLOCK ***** -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/menulist.css" type="text/css"?>
+
+<!DOCTYPE window SYSTEM "chrome://messenger/locale/joinChat.dtd">
+
+<dialog
+  id     = "joinChatDialog"
+  windowtype="Messenger:JoinChat"
+  title  = "&joinChatWindow.title;"
+  buttons= "accept,cancel"
+  xmlns  = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+  onload = "joinChat.onload()"
+  ondialogaccept="joinChat.join()">
+  <script type="application/javascript" src="chrome://messenger/content/chat/joinchat.js"/>
+
+  <grid>
+    <columns>
+      <column/>
+      <column flex="1"/>
+      <column id="optionalcolumn" labeltxt="&optional.label;"/>
+    </columns>
+    <rows>
+      <row id="accountBox" align="center">
+        <label value="&account.label;" control="accountlist"/>
+        <menulist id="accountlist" onselect="joinChat.onAccountSelect();"/>
+      </row>
+      <row id="separatorRow1">
+        <separator class="thin"/>
+      </row>
+      <row id="nameBox" align="baseline">
+        <label value="&name.label;" control="name"/>
+        <textbox id="name"/>
+      </row>
+      <row id="separatorRow2">
+        <separator class="thin"/>
+      </row>
+      <row id="autojoinBox">
+        <spacer/>
+        <checkbox label="&autojoin.label;" accesskey="&autojoin.accesskey;" id="autojoin"/>
+      </row>
+    </rows>
+  </grid>
+</dialog>
new file mode 100644
--- /dev/null
+++ b/mail/components/im/im.manifest
@@ -0,0 +1,4 @@
+component {13118758-dad2-418c-a03d-1acbfed0cd01} imProtocolInfo.js
+contract @mozilla.org/messenger/protocol/info;1?type=im {13118758-dad2-418c-a03d-1acbfed0cd01}
+component {9dd7f36b-5960-4f0a-8789-f5f516bd083d} imIncomingServer.js
+contract @mozilla.org/messenger/server;1?type=im {9dd7f36b-5960-4f0a-8789-f5f516bd083d}
new file mode 100644
--- /dev/null
+++ b/mail/components/im/imIncomingServer.js
@@ -0,0 +1,249 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+Cu.import("resource:///modules/imXPCOMUtils.jsm");
+Cu.import("resource:///modules/imServices.jsm");
+
+function imIncomingServer() { }
+
+imIncomingServer.prototype = {
+  get wrappedJSObject() this,
+  _imAccount: null,
+  get imAccount() {
+    if (this._imAccount)
+      return this._imAccount;
+
+    let id = this.getCharValue("imAccount");
+    if (!id)
+      return null;
+    Services.core.init();
+    return (this._imAccount = Services.accounts.getAccountById(id));
+  },
+  set imAccount(aImAccount) {
+    this._imAccount = aImAccount;
+    this.setCharValue("imAccount", aImAccount.id);
+  },
+  _prefBranch: null,
+  valid: true,
+  _key: "",
+  get key() this._key,
+  set key(aKey) {
+    this._key = aKey;
+    this._prefBranch = Services.prefs.getBranch("mail.server." + aKey + ".");
+  },
+  equals: function(aServer)
+    "wrappedJSObject" in aServer && aServer.wrappedJSObject == this,
+
+  clearAllValues: function() {
+    Services.accounts.deleteAccount(this.imAccount.id);
+    this._prefBranch.deleteBranch("");
+    delete this._prefBranch;
+    delete this._imAccount;
+  },
+  // called by nsMsgAccountManager while deleting an account:
+  forgetSessionPassword: function() { },
+
+  // Shown in the "Remove Account" confirm prompt.
+  get prettyName() this.imAccount.protocol.name + " - " + this.imAccount.name,
+
+  //XXX Flo: I don't think these 2 names are visible in the UI:
+  get constructedPrettyName() "constructedPrettyName FIXME",
+  realHostName: "realHostName FIXME",
+
+  port: 0,
+  accountManagerChrome: "am-im.xul",
+
+
+  //FIXME need a new imIIncomingService iface + classinfo for these 3 properties :(
+  get password() this.imAccount.password,
+  set password(aPassword) {
+    this.imAccount.password = aPassword;
+  },
+  get alias() this.imAccount.alias,
+  set alias(aAlias) {
+    this.imAccount.alias = aAlias;
+  },
+  get autojoin() {
+    try {
+      let prefName = "messenger.account." + this.imAccount.id + ".autoJoin";
+      return Services.prefs.getCharPref(prefName);
+    } catch (e) {
+      return "";
+    }
+  },
+  set autojoin(aAutojoin) {
+    let prefName = "messenger.account." + this.imAccount.id + ".autoJoin";
+    Services.prefs.setCharPref(prefName, aAutojoin);
+  },
+
+  // This is used for user-visible advanced preferences.
+  setUnicharValue: function(aPrefName, aValue) {
+    if (aPrefName == "autojoin")
+      this.autojoin = aValue;
+    else if (aPrefName == "alias")
+      this.alias = aValue;
+    else if (aPrefName == "password")
+      this.password = aValue;
+    else
+      this.imAccount.setString(aPrefName, aValue);
+  },
+  getUnicharValue: function(aPrefName) {
+    if (aPrefName == "autojoin")
+      return this.autojoin;
+    if (aPrefName == "alias")
+      return this.alias;
+    if (aPrefName == "password")
+      return this.password;
+
+    try {
+      let prefName =
+        "messenger.account." + this.imAccount.id + ".options." + aPrefName;
+      return Services.prefs.getCharPref(prefName);
+    } catch (x) {
+      return this._getDefault(aPrefName);
+    }
+  },
+  setBoolValue: function(aPrefName, aValue) {
+    this.imAccount.setBool(aPrefName, aValue);
+  },
+  getBoolValue: function(aPrefName) {
+    try {
+      let prefName =
+        "messenger.account." + this.imAccount.id + ".options." + aPrefName;
+      return Services.prefs.getBoolPref(prefName);
+    } catch (x) {
+      return this._getDefault(aPrefName);
+    }
+  },
+  setIntValue: function(aPrefName, aValue) {
+    this.imAccount.setInt(aPrefName, aValue);
+  },
+  getIntValue: function(aPrefName) {
+    try {
+      let prefName =
+        "messenger.account." + this.imAccount.id + ".options." + aPrefName;
+      return Services.prefs.getIntPref(prefName);
+    } catch (x) {
+      return this._getDefault(aPrefName);
+    }
+  },
+  _defaultOptionValues: null,
+  _getDefault: function(aPrefName) {
+    if (this._defaultOptionValues)
+      return this._defaultOptionValues[aPrefName];
+
+    this._defaultOptionValues = {};
+    let options = this.imAccount.protocol.getOptions();
+    while (options.hasMoreElements()) {
+      let opt = options.getNext();
+      let type = opt.type;
+      if (type == opt.typeBool)
+        this._defaultOptionValues[opt.name] = opt.getBool();
+      else if (type == opt.typeInt)
+        this._defaultOptionValues[opt.name] = opt.getInt();
+      else if (type == opt.typeString)
+        this._defaultOptionValues[opt.name] = opt.getString();
+      else if (type == opt.typeList)
+        this._defaultOptionValues[opt.name] = opt.getListDefault();
+    }
+    return this._defaultOptionValues[aPrefName];
+  },
+
+  // the "Char" type will be used only for "imAccount" and internally.
+  setCharValue: function(aPrefName, aValue) {
+    this._prefBranch.setCharPref(aPrefName, aValue);
+  },
+  getCharValue: function(aPrefName) {
+    try {
+      return this._prefBranch.getCharPref(aPrefName);
+    } catch (x) {
+      return "";
+    }
+  },
+
+  get type() this._prefBranch.getCharPref("type"),
+  set type(aType) {
+    this._prefBranch.setCharPref("type", aType);
+  },
+
+  get username() this._prefBranch.getCharPref("username"),
+  set username(aUsername) {
+    if (!aUsername) {
+      // FIXME
+      // We are sometimes called with a null aUsername value. The JS
+      // stack doesn't help at all to understand where this comes
+      // from, as the next frame is in gloda.js which doesn't touch
+      // the username at all.
+      // The gloda line that's blamed is |msgAccountManager.allIdentities.Count()|
+      Components.utils.reportError("Attempting to set a null username to an imIncomingServer");
+      return;
+    }
+    this._prefBranch.setCharPref("username", aUsername);
+  },
+
+  get hostName() this._prefBranch.getCharPref("hostname"),
+  set hostName(aHostName) {
+    this._prefBranch.setCharPref("hostname", aHostName);
+  },
+
+  writeToFolderCache: function() { },
+  closeCachedConnections: function() { },
+  shutdown: function() { },
+  setFilterList: function() { },
+
+  get canBeDefaultServer() false,
+
+  // AccountManager.js verifies that spamSettings is non-null before
+  // using the initialize method, but we can't just use a null value
+  // because that would crash nsMsgPurgeService::PerformPurge which
+  // only verifies the nsresult return value of the spamSettings
+  // getter before accessing the level property.
+  get spamSettings() {
+    return {
+      level: 0,
+      initialize: function(aServer) {}
+    };
+  },
+
+  // nsMsgDBFolder.cpp crashes in HandleAutoCompactEvent if this doesn't exist:
+  msgStore: {
+    supportsCompaction: false
+  },
+
+  get serverURI() "im://" + this.imAccount.protocol.id + "/" + this.imAccount.normalizedName,
+  _rootFolder: null,
+  get rootFolder() {
+    if (this._rootFolder)
+      return this._rootFolder;
+
+    return (this._rootFolder = {
+      isServer: true,
+      server: this,
+      get prettyName() this.server.prettyName, // used in the account manager tree
+      get prettiestName() this.server.prettyName + " prettiestName", // never displayed?
+      get name() this.server.prettyName + " name", // never displayed?
+      // used in the folder pane tree, if we don't hide the IM accounts:
+      get abbreviatedName() this.server.prettyName + "abbreviatedName",
+      AddFolderListener: function() {},
+      RemoveFolderListener: function() {},
+      ListDescendents: function(descendents) {},
+      getFolderWithFlags: function(aFlags) null,
+      getFoldersWithFlags: function(aFlags)
+        Components.classes["@mozilla.org/array;1"]
+                  .createInstance(Components.interfaces.nsIMutableArray),
+      get subFolders() EmptyEnumerator,
+      getStringProperty: function(aPropertyName) "",
+      getNumUnread: function(aDeep) 0,
+      Shutdown: function() {}
+    });
+  },
+
+  classDescription: "IM Msg Incoming Server implementation",
+  classID: Components.ID("{9dd7f36b-5960-4f0a-8789-f5f516bd083d}"),
+  contractID: "@mozilla.org/messenger/server;1?type=im",
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIMsgIncomingServer])
+};
+
+const NSGetFactory = XPCOMUtils.generateNSGetFactory([imIncomingServer]);
new file mode 100644
--- /dev/null
+++ b/mail/components/im/imProtocolInfo.js
@@ -0,0 +1,31 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function imProtocolInfo() { }
+
+imProtocolInfo.prototype = {
+
+  defaultLocalPath: null,
+  get serverIID() null,
+  get requiresUsername() true,
+  get preflightPrettyNameWithEmailAddress() false,
+  get canDelete() true,
+  get canLoginAtStartUp() true,
+  get canDuplicate() false,
+  getDefaultServerPort: function() 0,
+  get canGetMessages() false,
+  get canGetIncomingMessages() false,
+  get defaultDoBiff() false,
+  get showComposeMsgLink() false,
+
+  classDescription: "IM Msg Protocol Info implementation",
+  classID: Components.ID("{13118758-dad2-418c-a03d-1acbfed0cd01}"),
+  contractID: "@mozilla.org/messenger/protocol/info;1?type=im",
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIMsgProtocolInfo])
+};
+
+const NSGetFactory = XPCOMUtils.generateNSGetFactory([imProtocolInfo]);
new file mode 100644
--- /dev/null
+++ b/mail/components/im/jar.mn
@@ -0,0 +1,61 @@
+messenger.jar:
+% overlay chrome://messenger/content/messenger.xul chrome://messenger/content/chat/chat-messenger-overlay.xul
+*   content/messenger/chat/chat-messenger-overlay.xul    (content/chat-messenger-overlay.xul)
+    content/messenger/chat/chat-messenger-overlay.js     (content/chat-messenger-overlay.js)
+    content/messenger/chat/chat.css                      (content/chat.css)
+    content/messenger/am-im.js                           (content/am-im.js)
+    content/messenger/am-im.xul                          (content/am-im.xul)
+    content/messenger/chat/addbuddy.js                   (content/addbuddy.js)
+    content/messenger/chat/addbuddy.xul                  (content/addbuddy.xul)
+    content/messenger/chat/joinchat.js                   (content/joinchat.js)
+    content/messenger/chat/joinchat.xul                  (content/joinchat.xul)
+    content/messenger/chat/imAccount.xml                 (content/imAccount.xml)
+    content/messenger/chat/imAccounts.css                (content/imAccounts.css)
+    content/messenger/chat/imAccounts.js                 (content/imAccounts.js)
+    content/messenger/chat/imAccounts.xul                (content/imAccounts.xul)
+    content/messenger/chat/imAccountWizard.xul           (content/imAccountWizard.xul)
+    content/messenger/chat/imAccountWizard.js            (content/imAccountWizard.js)
+*   content/messenger/chat/imContextMenu.js              (content/imContextMenu.js)
+    content/messenger/chat/imStatusSelector.js           (content/imStatusSelector.js)
+    content/messenger/chat/imbuddytooltip.xml            (content/imbuddytooltip.xml)
+    content/messenger/chat/imcontact.xml                 (content/imcontact.xml)
+*   content/messenger/chat/imconversation.xml            (content/imconversation.xml)
+    content/messenger/chat/imconv.xml                    (content/imconv.xml)
+    content/messenger/chat/imgroup.xml                   (content/imgroup.xml)
+    content/messenger/chat/imsearch.xml                  (content/imsearch.xml)
+% skin messenger-messagestyles classic/1.0 %skin/classic/messenger/messages/
+	skin/classic/messenger/messages/Bitmaps/minus-hover.png       (messages/Bitmaps/minus-hover.png)
+	skin/classic/messenger/messages/Bitmaps/minus.png             (messages/Bitmaps/minus.png)
+	skin/classic/messenger/messages/Bitmaps/plus-hover.png        (messages/Bitmaps/plus-hover.png)
+	skin/classic/messenger/messages/Bitmaps/plus.png              (messages/Bitmaps/plus.png)
+	skin/classic/messenger/messages/Footer.html                   (messages/Footer.html)
+	skin/classic/messenger/messages/Incoming/Content.html         (messages/Incoming/Content.html)
+	skin/classic/messenger/messages/Incoming/Context.html         (messages/Incoming/Context.html)
+	skin/classic/messenger/messages/Incoming/NextContent.html     (messages/Incoming/NextContent.html)
+	skin/classic/messenger/messages/Info.plist                    (messages/Info.plist)
+	skin/classic/messenger/messages/main.css                      (messages/main.css)
+	skin/classic/messenger/messages/NextStatus.html               (messages/NextStatus.html)
+	skin/classic/messenger/messages/Status.html                   (messages/Status.html)
+% skin messenger-emoticons classic/1.0 %skin/classic/messenger/smileys/
+	skin/classic/messenger/smileys/theme.js              (smileys/theme.js)
+	skin/classic/messenger/smileys/angry.png             (smileys/angry.png)
+	skin/classic/messenger/smileys/confused.png          (smileys/confused.png)
+	skin/classic/messenger/smileys/cool.png              (smileys/cool.png)
+	skin/classic/messenger/smileys/cry.png               (smileys/cry.png)
+	skin/classic/messenger/smileys/embarrassed.png       (smileys/embarrassed.png)
+	skin/classic/messenger/smileys/grin.png              (smileys/grin.png)
+	skin/classic/messenger/smileys/heart.png             (smileys/heart.png)
+	skin/classic/messenger/smileys/manga_annoyed.png     (smileys/manga_annoyed.png)
+	skin/classic/messenger/smileys/manga_embarrassed.png (smileys/manga_embarrassed.png)
+	skin/classic/messenger/smileys/manga_smile.png       (smileys/manga_smile.png)
+	skin/classic/messenger/smileys/manga_stunned.png     (smileys/manga_stunned.png)
+	skin/classic/messenger/smileys/manga_tired.png       (smileys/manga_tired.png)
+	skin/classic/messenger/smileys/sad.png               (smileys/sad.png)
+	skin/classic/messenger/smileys/shocked.png           (smileys/shocked.png)
+	skin/classic/messenger/smileys/slant.png             (smileys/slant.png)
+	skin/classic/messenger/smileys/slant2.png            (smileys/slant2.png)
+	skin/classic/messenger/smileys/smile.png             (smileys/smile.png)
+	skin/classic/messenger/smileys/sp_laugh.png          (smileys/sp_laugh.png)
+	skin/classic/messenger/smileys/straight_face.png     (smileys/straight_face.png)
+	skin/classic/messenger/smileys/tongue.png            (smileys/tongue.png)
+	skin/classic/messenger/smileys/wink.png              (smileys/wink.png)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..93a69cc789aeeba65a66e31418160bbbc9bef692
GIT binary patch
literal 620
zc$@)j0+aoTP)<h;3K|Lk000e1NJLTq000sI000yS1^@s6fR}Ab00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10qIFZK~y-6rIf#l8c`I7zdPeNBr?nliWY*6Rf;8}_W`mHi{ud!
zpCSb87GmKxG4CLRg<zK=g6VBi><S@E8k_L}#$htWW?XNIoe|gQy2<|F!u@mZ$L}7_
zJwhqPwuBG@K<zr*Be5NuLbUOC{G?bcz5<#+BR#$V&Vi3o%Ab2dLI}}nwTy1J`_?qg
zcR+rRfh*ty_#mZR-2!EhUa$8>*Yy^F>$=#sO`%W#AdX|gFeC_q4EPmzDWzNqrPTU4
zlgXrGn&zot7}RPtEX&$yCyF9`-zP~DfR2>%eFoA-qtQ{hT>b{+>-G9x$R=zyn`O^z
zYN;FHIL>Pz@4D_m2!Lf-xULIe0&Okb7n-Iu0BqYn2+6b&+stJkUDuxi>%>l{(>o!a
z=dELbh6adqJV==%mdoXL!!Vx5ag5_Qc%FCgavaCm^g;s^ppqn+0)%0BZx<V}=UKAV
zd_MmK{0@SED2fh3qA1D=^9ndygWB!(kHunf3gG+xov^GRQ=LdDFV_VL<f_%`qv3G)
z*)+{(@PID@7?n!p@nA4`Q!14@LWuwSw15s6fZ6Z&kB*OzU$DE4oJlD!H_3mskrb8#
za!RRRdlR*#+~z_lrGyaI+?2%ieIam@ms?8P>k3%@3+N9M9LSaNI{$+J0000<MNUMn
GLSTZ^*b&A6
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..72107d151f97907425c33874e16c7f2ac0c99e0b
GIT binary patch
literal 619
zc$@)i0+juUP)<h;3K|Lk000e1NJLTq000sI000yS1^@s6fR}Ab00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10q99YK~y-6rIbI58bKI`pV?h?v+IJ*st9%qLy9Bl7f2Bc)7mBZ
z6d_=Dkm?YEo!=lF1iOG~(urM)J0avqVPR!qC2V%ti+fIr+m$_|=Oy=pf%)^!!*5>R
zd4*DnT?rusfZBI>Kw>vGfoPM-<f-eruYe}dNRBUo3*e)a^5;R25JI%u?Od<dduN*F
zdmw+nz%_6R^re)WJD?QO@Atpxy50hC90%LBF-;SID2fPz0N?jh;8)<KlyW1KQrqWD
zr_-)!nrDV#P%f9TENicwFbr8Pm&9=l(3MhtNI}|oJU*#ZD&K&7tyVh-$-)+kMf%L9
zmbek6Qt362cO2&^1i-Q^9LE7D0B^KpU+B8t0I+TQC?r)TwrONQ&wy=WXS3P85YO|r
zu|PuuL^2+w)DdwUf6wJ|FQO=-SS;ds-qFiZ6s6Nk4N!nuuh&z6AP64pA`^R&CR@$t
z^G`~t-@fk?hT&027=~$KUIXV_P^Z)Rv0AOp0G7+;y|A<(6P-#aueJpV81;Jn$!IkC
zTqqQt!y~>3kgHa!)^Ip{>$+}N2=RZP7SMqlP#6pbC#_bi&HgrWE~UK6lK*NW3CsWt
zrPQy3iQ18oT_~lL5aNd0lDK&&1a9+kM@f6#0P(+o{s4i7%Y)2RiiiLJ002ovPDHLk
FV1l8B4mAJ(
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..4509b17c0e9d2bf70c6dff3f17386d721eabb493
GIT binary patch
literal 615
zc$@)e0+{`YP)<h;3K|Lk000e1NJLTq000sI000yS1^@s6fR}Ab00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10pv+UK~y-6rIfLX8c`I6zdPeNBr?nliWY*6Rf;8}_W`o77Re(d
zd5RFQ3#^4{V%|Xr3&Ac$1cR+jid`WP(%9$&48vrKjjlI^b7kCUb`yV?V(vYB{O9M~
zBa~9ymJmV!sC}0QByQKH5MwkNJt`K9&w(b;NUyJeOW=c)^2b4t5JI$CEvwV%ytZxo
z9LOIqa1Fcx-b*RhJ0K0|cDtWV(`*6wzK`p=6bc0Zk|ZIDBEm4#z%RfvDdk!yrMB%%
zCX=>p+ixw)qE@TnIL=->aU3(B&si>)0BtGdI}I|1!{JG}T>c8=>-G8$WICM!5Cp*v
zY&M(eHk(H3MtGk00?7Nmf6z%*j^p6_K7b9pG<42}VKe|-*S!;>m8os!G{`i~$G|pN
z-A-27vmgkzwLrrFMEV^i?TFQC_06)Zvm{CIJZ~r1ePP3998z5wfCAKVxtsz-QFLz?
znc7PowpuI}AAw(C7!t?vosc+=^<Z8D7djsU$dAY4w&!{0M-3#9t5&NI27|#T+qR#;
z5l;lLDwWE^e!u^!R4TQF5dZgR0TZwQyVvWToSvROW&awvkWzlnlK)sEDJ%!%lu|$c
z7I;><?2l4P2_bISjKs}-A+X71N9lT91FL@l{RR@j%iQoSz1RQ%002ovPDHLkV1lbS
B1%&_r
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..eaf364177d64f34204a1578c151dc027b68ce72d
GIT binary patch
literal 614
zc$@)d0-61ZP)<h;3K|Lk000e1NJLTq000sI000yS1^@s6fR}Ab00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10pm$TK~y-6rIf#l8c`I+e|P@qWX6HD7!|>8;F4kq`T{9pVICpL
zQ-pwBV5=YmJKrEI1iOG~(uswTVpj-+6c$z%wv!oV<E%G@Gcs;8yNMr6G4~#ReCOlb
zBa~9yl@LMzs6&@WB<|Lx5N$G<JoY^A1<(ST>Gd^m1$>ZF{x}K}LWoYMWA=KzH<o2x
z0EHt4Zh*HyUrM>z17(nYzyDd+^)`U(x;T!5Wmy2kaZDJ71VNAizW~prlpCRx+O;#A
z&AOIly)z7hN~MBr+XwALQN(JsB1sZ}u9Wh92GYjk@p-jc{R$N7_4*!UKA!{N`~Dtm
zxm;##wzSlZI6FIg2^3t{J?bPc+qQ9C7oZ5d(z2X&U2g(7j&m<0Q?9mU<Umh=U9ed@
zdF9W1-`~{&O$`v~cakzkBuVnkG|gvm98)Tl_JTbWHf+Zs)wKpFK&{v789*3@4|b8O
zy~@H?i^bxjQtDR_1VmAEFC>bhY%p(t%Pb!aC`_l*Zn<2(IB6gOqtR$Q8jVJuipAnn
zIN^x^X02AUhr{7(&-1!Mi2r-EfDV{IaWEL1x7+Owhu6rZl=6F?{KpzeVFq9*rGEY`
z@Vt!tk5WnrA#T}@#O*^Nu+3#p>3ZD&$-jVp1N9Wn>y(Db;s5{u07*qoM6N<$g7y#w
AbN~PV
new file mode 100644
--- /dev/null
+++ b/mail/components/im/messages/Footer.html
@@ -0,0 +1,171 @@
+<script type="text/javascript;version=1.7">
+function groupEvents(parent)
+{
+  var P_list = parent.getElementsByClassName("event");
+
+  if (P_list.length < 4)
+    return;
+
+  var HR_list = parent.getElementsByTagName("hr");
+
+  if (P_list[1].hasAttribute("style"))
+  {
+    var prevStyle = P_list[1].style.display;
+    var nbEvents = P_list.length;
+
+    P_list[nbEvents-2].style.display = prevStyle;
+    HR_list[nbEvents-3].style.display = prevStyle;
+  }
+  else
+  {
+    var nbEvents = P_list.length;
+
+    for (var i=1; i < (nbEvents-2); i++)
+    {
+      P_list[i].style.display = "none";
+      HR_list[i].style.display = "none";
+    }
+    P_list[i].style.display = "none";
+
+    var p = document.createElement("p");
+    p.addEventListener("click", toggle_groupEvents);
+    p.setAttribute("class", "button hide");
+    parent.insertBefore(p, P_list[1]);
+  }
+}
+
+
+function toggle_groupEvents(aEvent)
+{
+  if (aEvent.detail > 1)
+    return;
+
+  var target = aEvent.target;
+  var P_list = target.parentNode.getElementsByClassName("event");
+  var HR_list = target.parentNode.getElementsByTagName("hr");
+  var nbEvents = P_list.length;
+
+  if (target.className == "button hide") {
+    for (var i=1; i < (nbEvents-2); i++) {
+      P_list[i].style.display = "block";
+      HR_list[i].style.display = "block";
+    }
+    P_list[i].style.display = "block";
+
+    target.className = "button show";
+  }
+  else {
+    for (var i=1; i < (nbEvents-2); i++) {
+      P_list[i].style.display = "none";
+      HR_list[i].style.display = "none";
+    }
+    P_list[i].style.display = "none";
+
+    target.className = "button hide";
+  }
+}
+
+function prettyPrintTime(aValue, aNoSeconds) {
+  if (aValue < 60 && aNoSeconds)
+    return "";
+
+  if (aNoSeconds)
+    aValue -= aValue % 60;
+
+  let valuesAndUnits = window.convertTimeUnits(aValue);
+  if (!valuesAndUnits[2])
+    valuesAndUnits.splice(2, 2);
+  return valuesAndUnits.join(" ");
+}
+
+const shadow = 3;
+const coef = 3;
+const timebeforetextdisplay = 5 * 60;
+
+function computeSpace(aInterval)
+  Math.round(coef * Math.log(aInterval + 1))
+
+function refreshIntervals()
+{
+  var pixels = 1;
+  var previousTime = 0;
+  while (true) {
+    var time = Math.exp((pixels - 0.49999) / coef) - 1;
+    if (time >= timebeforetextdisplay)
+      break;
+
+    time = Math.round(time * 1000);
+    var result = time - previousTime;
+    previousTime = time;
+    yield result;
+    ++pixels;
+  }
+  while (true)
+    yield 60 * 1000;
+}
+
+var lastMessageTimeout;
+function handleLastMessage(aGenerator)
+{
+  var interval = Math.round(Date.now() / 1000) - lastInsertTime;
+  var p = document.getElementById("lastMessage");
+  var margin = computeSpace(interval);
+  var text = "";
+  if (interval >= timebeforetextdisplay) {
+    p.style.lineHeight = (margin + shadow) + "px";
+    p.setAttribute("class", "interval");
+    text = prettyPrintTime(interval, true);
+    margin = 0;
+  }
+  p.textContent = text;
+  p.style.marginTop = (margin - shadow) + "px";
+  var body = document.getElementsByTagName("body")[0];
+  if (body.scrollHeight <= body.scrollTop + body.clientHeight + p.clientHeight + 10)
+    p.scrollIntoView(true);
+  lastMessageTimeout =
+    setTimeout(handleLastMessage, aGenerator.next(), aGenerator);
+}
+
+var lastInsertTime = 0;
+function checkNewText(aEvent)
+{
+  var target = aEvent.originalTarget;
+  if (!(target instanceof HTMLElement))
+    return;
+
+  if (target.tagName == "DIV" && target.className.substring(0, 6) == "bubble") {
+    var insertTime = document.getElementById("insert").getAttribute("time");
+    if (lastInsertTime && insertTime >= lastInsertTime) {
+      var interval = insertTime - lastInsertTime;
+      var margin = computeSpace(interval);
+      if (interval >= timebeforetextdisplay) {
+        var p = document.createElement("p");
+        p.style.lineHeight = (margin + shadow) + "px";
+        p.style.marginTop = -shadow + "px";
+        p.setAttribute("class", "interval");
+        p.textContent = prettyPrintTime(interval);
+        target.parentNode.insertBefore(p, target);
+        margin = 0;
+      }
+      target.style.marginTop = margin + "px";
+    }
+    lastInsertTime = insertTime;
+  }
+  else if (target.tagName == "DIV" && target.id == "insert") {
+    lastInsertTime = document.getElementById("insert").getAttribute("time");
+  }
+  else if (target.tagName == "P" && target.className == "event") {
+    groupEvents(target.parentNode);
+    lastInsertTime = document.getElementById("insert").getAttribute("time");
+  }
+  if (lastInsertTime) {
+    clearTimeout(lastMessageTimeout);
+    lastMessageTimeout = setTimeout(handleLastMessage, 0, refreshIntervals());
+  }
+}
+
+document.getElementById("ibcontent")
+        .addEventListener("DOMNodeInserted", checkNewText);
+</script>
+
+<p id="lastMessage"/>
new file mode 100644
--- /dev/null
+++ b/mail/components/im/messages/Incoming/Content.html
@@ -0,0 +1,7 @@
+<div class="bubble %messageClasses%" senderColor="%senderColor%">
+<div class="indicator">
+<p class="pseudo"><span class="sender">%sender%<span class="time">%time{%H:%M}%</span></span></p>
+<p class="%messageClasses%" title="%time%">%message%</p>
+<div id="insert" time="%timestamp%"/>
+</div>
+</div>
new file mode 100644
--- /dev/null
+++ b/mail/components/im/messages/Incoming/Context.html
@@ -0,0 +1,7 @@
+<div class="bubble context %messageClasses%" senderColor="%senderColor%">
+<div class="indicator">
+<p class="pseudo"><span class="sender">%sender%<span class="time">%time{%H:%M}%</span></span></p>
+<p class="%messageClasses%" title="%time%">%message%</p>
+<div id="insert" time="%timestamp%"/>
+</div>
+</div>
new file mode 100644
--- /dev/null
+++ b/mail/components/im/messages/Incoming/NextContent.html
@@ -0,0 +1,3 @@
+<hr/>
+<p class="%messageClasses%" title="%time%">%message%</p>
+<div id="insert" time="%timestamp%"/>
new file mode 100644
--- /dev/null
+++ b/mail/components/im/messages/Info.plist
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+  <key>ActionMessageTemplate</key>
+  <string>%sender% %message%</string>
+  
+  <key>CFBundleDevelopmentRegion</key>
+  <string>English</string>
+  
+  <key>CFBundleGetInfoString</key>
+  <string>Instantbird Bubbles Message Style</string>
+  
+  <key>CFBundleIdentifier</key>
+  <string>org.instantbird.bubbles.message.style</string>
+  
+  <key>CFBundleInfoDictionaryVersion</key>
+  <string>1.0</string>
+  
+  <key>CFBundleName</key>
+  <string>Bubbles</string>
+  
+  <key>CFBundlePackageType</key>
+  <string>AdIM</string>
+  
+  <key>DefaultBackgroundColor</key>
+  <string>FFFFFF</string>
+  
+  <key>DisableCustomBackground</key>
+  <false/>
+  
+  <key>MessageViewVersion</key>
+  <integer>4</integer>
+  
+  <key>ShowsUserIcons</key>
+  <true/>
+</dict>
+</plist>
new file mode 100644
--- /dev/null
+++ b/mail/components/im/messages/NextStatus.html
@@ -0,0 +1,3 @@
+<hr/>
+<p class="%messageClasses%">%time% - %message%</p>
+<div id="insert" time="%timestamp%"/>
new file mode 100644
--- /dev/null
+++ b/mail/components/im/messages/Status.html
@@ -0,0 +1,4 @@
+<div class="bubble %messageClasses%">
+<p class="%messageClasses%">%time% - %message%</p>
+<div id="insert" time="%timestamp%"/>
+</div>
new file mode 100644
--- /dev/null
+++ b/mail/components/im/messages/main.css
@@ -0,0 +1,184 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2010.
+ *
+ * The Initial Developer of the Original Code is
+ * Quentin Castier <idechix@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Benedikt Pfeifer <benediktp@ymail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+body {
+  margin: 0;
+  padding: 0;
+  background: #f3f3f2;
+}
+
+p {
+  font-family: sans-serif;
+  margin: 0;
+  padding: 0;
+}
+
+div.bubble {
+  margin: 20px 20px 3px 20px;
+  padding: 5px 0px;
+  border-width: 1px;
+  border-style: solid;
+  border-radius: 5px;
+  background-color: #fcfcfc;
+  border-color: #aeafac;
+}
+
+div.bubble:not(.context):not(.event) {
+  -moz-animation-duration: 0.5s;
+  -moz-animation-name: fadein;
+  -moz-animation-iteration-count: 1;
+}
+
+@-moz-keyframes fadein {
+  from {
+    opacity: 0;
+  }
+
+  to {
+    opacity: 1.0;
+  }
+}
+div.bubble>div.indicator {
+  margin: 0;
+}
+
+div.bubble.event {
+  background-color: hsl(0, 0%, 99%);
+  border-color: hsl(0, 0%, 85%);
+}
+
+div.bubble p.pseudo {
+  font-size: smaller;
+  font-weight: bold;
+}
+
+div.bubble p.pseudo>span>span.time {
+  display: block;
+  float: right;
+  margin-right: 10px;
+  color: #aeafac;
+  font-weight: normal;
+}
+
+div.bubble hr {
+  margin: 0;
+  height: 2px;
+  border-style: none;
+  border-top: 1px solid #ffffff;
+  border-bottom: 1px solid #dbddd7;
+}
+
+p.interval {
+  text-align: center;
+  color: hsl(0, 0%, 60%);
+}
+
+.message {
+  padding: 5px 10px;
+}
+
+#lastMessage {
+  line-height: 20px;
+  text-align: center;
+  color: hsl(0, 0%, 60%);
+  font-weight: bold;
+  text-shadow: 0px 1px 0px #ffffff;
+}
+
+p.nick {
+  font-weight: bold;
+}
+
+p.action {
+  font-style: italic;
+}
+
+p.action:before {
+  content: "*** ";
+}
+
+p.event {
+  color: hsl(0, 0%, 60%);
+}
+
+#Chat {
+  white-space: normal;
+}
+
+p *:-moz-any-link img {
+  margin-bottom: 1px;
+  border-bottom: solid 1px;
+}
+
+
+
+/* used by javascript */
+p.button {
+  cursor: pointer;
+  min-height: 20px;
+  margin-left: -24px;
+  margin-bottom: -3px;
+  padding-left: 24px;
+}
+
+p.hide {
+  background: url('Bitmaps/plus.png') no-repeat left top;
+}
+
+p.hide:hover {
+  background: url('Bitmaps/plus-hover.png') no-repeat left top;
+}
+
+p.hide:after {
+  content: "...";
+  color: hsl(0, 0%, 60%);
+}
+
+p.show {
+  background: url('Bitmaps/minus.png') no-repeat left top;
+  margin-bottom: -20px;
+  width: 0;
+}
+
+p.show:hover {
+  background: url('Bitmaps/minus-hover.png') no-repeat left top;
+}
+
+.sender {
+  margin-left: 10px;
+}
new file mode 100644
--- /dev/null
+++ b/mail/components/im/modules/index_im.js
@@ -0,0 +1,348 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Thunderbird.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@queze.net>.
+ * Portions created by the Initial Developer are Copyright (C) 2012
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const EXPORTED_SYMBOLS = [];
+
+const {classes: Cc, interfaces: Ci, utils: Cu, Constructor: CC} = Components;
+
+Cu.import("resource:///modules/imXPCOMUtils.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource:///modules/gloda/public.js");
+Cu.import("resource:///modules/gloda/indexer.js");
+Cu.import("resource:///modules/imServices.jsm");
+
+const kCacheFileName = "indexedFiles.json";
+
+const FileInputStream = CC("@mozilla.org/network/file-input-stream;1",
+                           "nsIFileInputStream",
+                           "init");
+const ScriptableInputStream = CC("@mozilla.org/scriptableinputstream;1",
+                                 "nsIScriptableInputStream",
+                                 "init");
+
+XPCOMUtils.defineLazyGetter(this, "MailFolder", function()
+  Cc["@mozilla.org/rdf/resource-factory;1?name=mailbox"].createInstance(Ci.nsIMsgFolder)
+);
+
+function GlodaIMConversation(aTitle, aTime, aPath, aContent)
+{
+  // grokNounItem from gloda.js puts automatically the values of all
+  // JS properties in the jsonAttributes magic attribute, except if
+  // they start with _, so we put the values in _-prefixed properties,
+  // and have getters in the prototype.
+  this._title = aTitle;
+  this._time = aTime;
+  this._path = aPath;
+  this._content = aContent;
+}
+GlodaIMConversation.prototype = {
+  get title() this._title,
+  get time() this._time,
+  get path() this._path,
+  get content() this._content,
+
+  // for glodaFacetBindings.xml compatibility (pretend we are a message object)
+  get subject() this._title,
+  get date() new Date(this._time * 1000),
+  get recipient() null,
+  get from() ({value: "", contact: {name: ""}}),
+  get starred() false,
+  get attachmentNames() null,
+  get indexedBodyText() this._content,
+  get read() true,
+
+  // for glodaFacetView.js _removeDupes
+  get headerMessageID() this.id
+};
+
+// FIXME
+var WidgetProvider = {
+  providerName: "widget",
+  process: function () {
+    //XXX What is this supposed to do?
+    yield Gloda.kWorkDone;
+  }
+};
+
+var IMConversationNoun = {
+  name: "im-conversation",
+  clazz: GlodaIMConversation,
+  allowsArbitraryAttrs: true,
+  tableName: "imConversations",
+  schema: {
+    columns: [['id', 'INTEGER PRIMARY KEY'],
+              ['title', 'STRING'],
+              ['time', 'NUMBER'],
+              ['path', 'STRING']
+             ],
+    fulltextColumns: [['content', 'STRING']]
+  }
+};
+Gloda.defineNoun(IMConversationNoun);
+
+// Needs to be set after calling defineNoun, otherwise it's replaced
+// by databind.js' implementation.
+IMConversationNoun.objFromRow = function(aRow) {
+  // Row columns are:
+  // 0 id
+  // 1 title
+  // 2 time
+  // 3 path
+  // 4 jsonAttributes
+  // 5 content
+  // 6 offsets
+  let conv = new GlodaIMConversation(aRow.getString(1), aRow.getInt64(2),
+                                     aRow.getString(3), aRow.getString(5));
+  conv.id = aRow.getInt64(0); // handleResult will keep only our first result
+                              // if the id property isn't set.
+  return conv;
+};
+
+const EXT_NAME = "im";
+
+// --- special (on-row) attributes
+Gloda.defineAttribute({
+  provider: WidgetProvider, extensionName: EXT_NAME,
+  attributeType: Gloda.kAttrFundamental,
+  attributeName: "time",
+  singular: true,
+  special: Gloda.kSpecialColumn,
+  specialColumnName: "time",
+  subjectNouns: [IMConversationNoun.id],
+  objectNoun: Gloda.NOUN_NUMBER,
+  canQuery: true
+});
+Gloda.defineAttribute({
+  provider: WidgetProvider, extensionName: EXT_NAME,
+  attributeType: Gloda.kAttrFundamental,
+  attributeName: "title",
+  singular: true,
+  special: Gloda.kSpecialString,
+  specialColumnName: "title",
+  subjectNouns: [IMConversationNoun.id],
+  objectNoun: Gloda.NOUN_STRING,
+  canQuery: true
+});
+Gloda.defineAttribute({
+  provider: WidgetProvider, extensionName: EXT_NAME,
+  attributeType: Gloda.kAttrFundamental,
+  attributeName: "path",
+  singular: true,
+  special: Gloda.kSpecialString,
+  specialColumnName: "path",
+  subjectNouns: [IMConversationNoun.id],
+  objectNoun: Gloda.NOUN_STRING,
+  canQuery: true
+});
+
+// --- fulltext attributes
+Gloda.defineAttribute({
+  provider: WidgetProvider, extensionName: EXT_NAME,
+  attributeType: Gloda.kAttrFundamental,
+  attributeName: "content",
+  singular: true,
+  special: Gloda.kSpecialFulltext,
+  specialColumnName: "content",
+  subjectNouns: [IMConversationNoun.id],
+  objectNoun: Gloda.NOUN_FULLTEXT,
+  canQuery: true
+});
+
+// -- fulltext search helper
+// fulltextMatches.  Match over message subject, body, and attachments
+// @testpoint gloda.noun.message.attr.fulltextMatches
+this._attrFulltext = Gloda.defineAttribute({
+  provider: WidgetProvider,
+  extensionName: EXT_NAME,
+  attributeType: Gloda.kAttrDerived,
+  attributeName: "fulltextMatches",
+  singular: true,
+  special: Gloda.kSpecialFulltext,
+  specialColumnName: "imConversationsText",
+  subjectNouns: [IMConversationNoun.id],
+  objectNoun: Gloda.NOUN_FULLTEXT
+});
+// For facet.js DateFaceter
+Gloda.defineAttribute({
+  provider: WidgetProvider, extensionName: EXT_NAME,
+  attributeType: Gloda.kAttrDerived,
+  attributeName: "date",
+  singular: true,
+  special: Gloda.kSpecialColumn,
+  subjectNouns: [IMConversationNoun.id],
+  objectNoun: Gloda.NOUN_NUMBER,
+  facet: {
+    type: "date"
+  },
+  canQuery: true
+});
+
+var GlodaIMIndexer = {
+  name: "index_im",
+  enable: function() {
+    //TODO start listening for incremental changes
+  },
+  disable: function() {
+    //TODO stop listening for incremental changes
+  },
+
+  _knownFiles: {},
+  _cacheSaveTimer: null,
+  _scheduleCacheSave: function() {
+    if (this._cacheSaveTimer)
+      return;
+    this._cacheSaveTimer = setTimeout(this._saveCacheNow, 5000);
+  },
+  _saveCacheNow: function() {
+    let data = JSON.stringify(GlodaIMIndexer._knownFiles);
+    let file = FileUtils.getFile("ProfD", ["logs", kCacheFileName]);
+    let ostream = FileUtils.openSafeFileOutputStream(file);
+
+    // Obtain a converter to convert our data to a UTF-8 encoded input stream.
+    let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Ci.nsIScriptableUnicodeConverter);
+    converter.charset = "UTF-8";
+
+    // Asynchronously copy the data to the file.
+    let istream = converter.convertToInputStream(data);
+    NetUtil.asyncCopy(istream, ostream, function(rc) {
+      if (!Components.isSuccessCode(rc)) {
+        dump("Failed to write cache file\n");
+      }
+    });
+  },
+
+  _worker_logsFolderSweep: function(aJob) {
+    let dir = FileUtils.getFile("ProfD", ["logs"]);
+    if (!dir.exists() || !dir.isDirectory())
+      return;
+
+    let cacheFile = dir.clone();
+    cacheFile.append(kCacheFileName);
+    if (cacheFile.exists()) {
+      const PR_RDONLY = 0x01;
+      let fis = new FileInputStream(cacheFile, PR_RDONLY, 0444,
+                                    Ci.nsIFileInputStream.CLOSE_ON_EOF);
+      let sis = new ScriptableInputStream(fis);
+      let text = sis.read(sis.available());
+      sis.close();
+      this._knownFiles = JSON.parse(text);
+    }
+
+    let children = dir.directoryEntries;
+    while (children.hasMoreElements()) {
+      let proto = children.getNext().QueryInterface(Ci.nsIFile);
+      if (!proto.isDirectory())
+        continue;
+      let protoName = proto.leafName;
+      if (!Object.prototype.hasOwnProperty.call(this._knownFiles, protoName))
+        this._knownFiles[protoName] = {};
+      let protoObj = this._knownFiles[protoName];
+      let accounts = proto.directoryEntries;
+      while (accounts.hasMoreElements()) {
+        let account = accounts.getNext().QueryInterface(Ci.nsIFile);
+        if (!account.isDirectory())
+          continue;
+        let accountName = account.leafName;
+        if (!Object.prototype.hasOwnProperty.call(protoObj, accountName))
+          protoObj[accountName] = {};
+        let accountObj = protoObj[accountName];
+        let convs = account.directoryEntries;
+        while (convs.hasMoreElements()) {
+          let conv = convs.getNext().QueryInterface(Ci.nsIFile);
+          let convName = conv.leafName;
+          if (!conv.isDirectory() || convName == ".system")
+            continue;
+          if (!Object.prototype.hasOwnProperty.call(accountObj, convName))
+            accountObj[convName] = {};
+          let job = new IndexingJob("convFolderSweep", null);
+          job.folder = conv;
+          job.convObj = accountObj[convName];
+          GlodaIndexer.indexJob(job);
+        }
+      }
+    }
+  },
+
+  _worker_convFolderSweep: function(aJob, aCallbackHandle) {
+    let folder = aJob.folder;
+    let cache = aJob.convObj;
+
+    let sessions = folder.directoryEntries;
+    while (sessions.hasMoreElements()) {
+      let file = sessions.getNext().QueryInterface(Ci.nsIFile);
+      let fileName = file.leafName;
+      if (!file.isFile() || !file.isReadable() || !/\.json$/.test(fileName))
+        continue;
+      let lastModifiedTime = file.lastModifiedTime;
+      if (Object.prototype.hasOwnProperty.call(cache, fileName) &&
+          cache[fileName] == lastModifiedTime) {
+        continue;
+      }
+      let log = Services.logs.getLogFromFile(file);
+      let conv = log.getConversation();
+      let content = conv.getMessages()
+                        .map(function(m) (m.alias || m.who) + ": " + MailFolder.convertMsgSnippetToPlainText(m.message))
+                        .join("\n\n");
+      let path = [folder.parent.parent.leafName, folder.parent.leafName, folder.leafName, fileName].join("/");
+      let glodaConv = new GlodaIMConversation(conv.title, log.time, path, content);
+      yield aCallbackHandle.pushAndGo(
+        Gloda.grokNounItem(glodaConv, {}, true, true, aCallbackHandle));
+      aJob.convObj[fileName] = lastModifiedTime;
+      this._scheduleCacheSave();
+      yield Gloda.kWorkSync;
+    }
+    yield Gloda.kWorkDone;
+  },
+
+  get workers() {
+    return [
+      ["logsFolderSweep", {
+         worker: this._worker_logsFolderSweep
+       }],
+       ["convFolderSweep", {
+         worker: this._worker_convFolderSweep
+       }]
+    ];
+  },
+
+  initialSweep: function() {
+    let job = new IndexingJob("logsFolderSweep", null);
+    GlodaIndexer.indexJob(job);
+  }
+};
+
+GlodaIndexer.registerIndexer(GlodaIMIndexer);
new file mode 100644
--- /dev/null
+++ b/mail/components/im/modules/search_im.js
@@ -0,0 +1,370 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ *   Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Thunderbird Global Database.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Andrew Sutherland <asutherland@asutherland.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const EXPORTED_SYMBOLS = ["GlodaIMSearcher"];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+Cu.import("resource:///modules/gloda/public.js");
+
+/**
+ * How much time boost should a 'score point' amount to?  The authoritative,
+ *  incontrivertible answer, across all time and space, is a week.
+ *  Note that gloda stores conversation timestamps in seconds.
+ */
+const FUZZSCORE_TIMESTAMP_FACTOR = 60 * 60 * 24 * 7;
+
+const RANK_USAGE =
+  "glodaRank(matchinfo(imConversationsText), 1.0, 2.0, 2.0, 1.5, 1.5)";
+
+const DASCORE ="imConversations.time";
+//  "(((" + RANK_USAGE + ") * " +
+//    FUZZSCORE_TIMESTAMP_FACTOR +
+//   ") + imConversations.time)";
+
+/**
+ * A new optimization decision we are making is that we do not want to carry
+ *  around any data in our ephemeral tables that is not used for whittling the
+ *  result set.  The idea is that the btree page cache or OS cache is going to
+ *  save us from the disk seeks and carrying around the extra data is just going
+ *  to be CPU/memory churn that slows us down.
+ *
+ * Additionally, we try and avoid row lookups that would have their results
+ *  discarded by the LIMIT.  Because of limitations in FTS3 (which might
+ *  be addressed in FTS4 by a feature request), we can't avoid the 'imConversations'
+ *  lookup since that has the message's date and static notability but we can
+ *  defer the 'imConversationsText' lookup.
+ *
+ * This is the access pattern we are after here:
+ * 1) Order the matches with minimized lookup and result storage costs.
+ * - The innermost MATCH does the doclist magic and provides us with
+ *    matchinfo() support which does not require content row retrieval
+ *    from imConversationsText.  Unfortunately, this is not enough to whittle anything
+ *    because we still need static interestingness, so...
+ * - Based on the match we retrieve the date and notability for that row from
+ *    'imConversations' using this in conjunction with matchinfo() to provide a score
+ *    that we can then use to LIMIT our results.
+ * 2) We reissue the MATCH query so that we will be able to use offsets(), but
+ *    we intersect the results of this MATCH against our LIMITed results from
+ *    step 1.
+ * - We use 'docid IN (phase 1 query)' to accomplish this because it results in
+ *    efficient lookup.  If we just use a join, we get O(mn) performance because
+ *    a cartesian join ends up being performed where either we end up performing
+ *    the fulltext query M times and table scan intersect with the results from
+ *    phase 1 or we do the fulltext once but traverse the entire result set from
+ *    phase 1 N times.
+ * - We believe that the re-execution of the MATCH query should have no disk
+ *    costs because it should still be cached by SQLite or the OS.  In the case
+ *    where memory is so constrained this is not true our behavior is still
+ *    probably preferable than the old way because that would have caused lots
+ *    of swapping.
+ * - This part of the query otherwise resembles the basic gloda query but with
+ *    the inclusion of the offsets() invocation.  The imConversations table lookup
+ *    should not involve any disk traffic because the pages should still be
+ *    cached (SQLite or OS) from phase 1.  The imConversationsText lookup is new, and
+ *    this is the major disk-seek reduction optimization we are making.  (Since
+ *    we avoid this lookup for all of the documents that were excluded by the
+ *    LIMIT.)  Since offsets() also needs to retrieve the row from imConversationsText
+ *    there is a nice synergy there.
+ */
+const NUEVO_FULLTEXT_SQL =
+  "SELECT imConversations.*, imConversationsText.*, offsets(imConversationsText) AS osets " +
+  "FROM imConversationsText, imConversations " +
+  "WHERE" +
+    " imConversationsText MATCH ?1 " +
+    " AND imConversationsText.docid IN (" +
+       "SELECT docid " +
+       "FROM imConversationsText JOIN imConversations ON imConversationsText.docid = imConversations.id " +
+       "WHERE imConversationsText MATCH ?1 " +
+       "ORDER BY " + DASCORE + " DESC " +
+       "LIMIT ?2" +
+    " )" +
+    " AND imConversations.id = imConversationsText.docid";
+
+function identityFunc(x) {
+  return x;
+}
+
+function oneLessMaxZero(x) {
+  if (x <= 1)
+    return 0;
+  else
+    return x - 1;
+}
+
+function reduceSum(accum, curValue) {
+  return accum + curValue;
+}
+
+/*
+ * Columns are: body, subject, attachment names, author, recipients
+ */
+
+/**
+ * Scores if all search terms match in a column.  We bias against author
+ *  slightly and recipient a bit more in this case because a search that
+ *  entirely matches just on a person should give a mention of that person
+ *  in the subject or attachment a fighting chance.
+ * Keep in mind that because of our indexing in the face of address book
+ *  contacts (namely, we index the name used in the e-mail as well as the
+ *  display name on the address book card associated with the e-mail adress)
+ *  a contact is going to bias towards matching multiple times.
+ */
+const COLUMN_ALL_MATCH_SCORES = [4, 20, 20, 16, 12];
+/**
+ * Score for each distinct term that matches in the column.  This is capped
+ *  by COLUMN_ALL_SCORES.
+ */
+const COLUMN_PARTIAL_PER_MATCH_SCORES = [1, 4, 4, 4, 3];
+/**
+ * If a term matches multiple times, what is the marginal score for each
+ *  additional match.  We count the total number of matches beyond the
+ *  first match for each term.  In other words, if we have 3 terms which
+ *  matched 5, 3, and 0 times, then the total from our perspective is
+ *  (5 - 1) + (3 - 1) + 0 = 4 + 2 + 0 = 6.  We take the minimum of that value
+ *  and the value in COLUMN_MULTIPLE_MATCH_LIMIT and multiply by the value in
+ *  COLUMN_MULTIPLE_MATCH_SCORES.
+ */
+const COLUMN_MULTIPLE_MATCH_SCORES = [1, 0, 0, 0, 0];
+const COLUMN_MULTIPLE_MATCH_LIMIT = [10, 0, 0, 0, 0];
+
+/**
+ * Score the message on its offsets (from stashedColumns).
+ */
+function scoreOffsets(aMessage, aContext) {
+  let score = 0;
+
+  let termTemplate = [0 for each (term in Iterator(aContext.terms, true))];
+  // for each column, a list of the incidence of each term
+  let columnTermIncidence = [termTemplate.concat(),
+                             termTemplate.concat(),
+                             termTemplate.concat(),
+                             termTemplate.concat(),
+                             termTemplate.concat()];
+
+  // we need a friendlyParseInt because otherwise the radix stuff happens
+  //  because of the extra arguments map parses.  curse you, map!
+  let offsetNums =
+    [parseInt(x) for each (x in aContext.stashedColumns[aMessage.id][0].split(" "))];
+  for (let i=0; i < offsetNums.length; i += 4) {
+    let columnIndex = offsetNums[i];
+    let termIndex = offsetNums[i+1];
+    columnTermIncidence[columnIndex][termIndex]++;
+  }
+
+  for (let iColumn = 0; iColumn < COLUMN_ALL_MATCH_SCORES.length; iColumn++) {
+    let termIncidence = columnTermIncidence[iColumn];
+    // bestow all match credit
+    if (termIncidence.every(identityFunc))
+      score += COLUMN_ALL_MATCH_SCORES[iColumn];
+    // bestow partial match credit
+    else if (termIncidence.some(identityFunc))
+      score += Math.min(COLUMN_ALL_MATCH_SCORES[iColumn],
+                        COLUMN_PARTIAL_PER_MATCH_SCORES[iColumn] *
+                          termIncidence.filter(identityFunc).length);
+    // bestow multiple match credit
+    score += Math.min(termIncidence.map(oneLessMaxZero).reduce(reduceSum, 0),
+                      COLUMN_MULTIPLE_MATCH_LIMIT[iColumn]) *
+             COLUMN_MULTIPLE_MATCH_SCORES[iColumn];
+  }
+
+  return score;
+}
+
+/**
+ * The searcher basically looks like a query, but is specialized for fulltext
+ *  search against imConversations.  Most of the explicit specialization involves
+ *  crafting a SQL query that attempts to order the matches by likelihood that
+ *  the user was looking for it.  This is based on full-text matches combined
+ *  with an explicit (generic) interest score value placed on the message at
+ *  indexing time (TODO).  This is followed by using the more generic gloda scoring
+ *  mechanism to explicitly score the IM conversations given the search context in
+ *  addition to the more generic score adjusting rules.
+ */
+function GlodaIMSearcher(aListener, aSearchString, aAndTerms) {
+  this.listener = aListener;
+
+  this.searchString = aSearchString;
+  this.fulltextTerms = this.parseSearchString(aSearchString);
+  this.andTerms = (aAndTerms != null) ? aAndTerms : true;
+
+  this.query = null;
+  this.collection = null;
+
+  this.scores = null;
+}
+GlodaIMSearcher.prototype = {
+  /**
+   * Number of messages to retrieve initially.
+   */
+  retrievalLimit: 400,
+
+  /**
+   * Parse the string into terms/phrases by finding matching double-quotes.
+   */
+  parseSearchString: function GlodaIMSearcher_parseSearchString(aSearchString) {
+    aSearchString = aSearchString.trim();
+    let terms = [];
+
+    /*
+     * Add the term as long as the trim on the way in didn't obliterate it.
+     *
+     * In the future this might have other helper logic; it did once before.
+     */
+    function addTerm(aTerm) {
+      if (aTerm)
+        terms.push(aTerm);
+    }
+
+    while (aSearchString) {
+      if (aSearchString[0] == '"') {
+        let endIndex = aSearchString.indexOf(aSearchString[0], 1);
+        // eat the quote if it has no friend
+        if (endIndex == -1) {
+          aSearchString = aSearchString.substring(1);
+          continue;
+        }
+
+        addTerm(aSearchString.substring(1, endIndex).trim());
+        aSearchString = aSearchString.substring(endIndex + 1);
+        continue;
+      }
+
+      let spaceIndex = aSearchString.indexOf(" ");
+      if (spaceIndex == -1) {
+        addTerm(aSearchString);
+        break;
+      }
+
+      addTerm(aSearchString.substring(0, spaceIndex));
+      aSearchString = aSearchString.substring(spaceIndex+1);
+    }
+
+    return terms;
+  },
+
+  buildFulltextQuery: function GlodaIMSearcher_buildFulltextQuery() {
+    let query = Gloda.newQuery(Gloda.lookupNoun("im-conversation"), {
+      noMagic: true,
+      explicitSQL: NUEVO_FULLTEXT_SQL,
+      limitClauseAlreadyIncluded: true,
+      // osets is 0-based column number 4 (volatile to column changes)
+      // save the offset column for extra analysis
+      stashColumns: [6]
+    });
+
+    let fulltextQueryString = "";
+
+    for each (let [iTerm, term] in Iterator(this.fulltextTerms)) {
+      if (iTerm)
+        fulltextQueryString += this.andTerms ? " " : " OR ";
+
+      // Put our term in quotes.  This is needed for the tokenizer to be able
+      //  to do useful things.  The exception is people clever enough to use
+      //  NEAR.
+      if (/^NEAR(\/\d+)?$/.test(term))
+        fulltextQueryString += term;
+      // Check if this is a single-character CJK search query.  If so, we want
+      //  to add a wildcard.
+      // Our tokenizer treats anything at/above 0x2000 as CJK for now.
+      else if (term.length == 1 && term.charCodeAt(0) >= 0x2000)
+        fulltextQueryString += term + "*";
+      else if (
+          term.length == 2 &&
+            term.charCodeAt(0) >= 0x2000 &&
+            term.charCodeAt(1) >= 0x2000
+          || term.length >= 3
+      )
+        fulltextQueryString += '"' + term + '"';
+
+    }
+
+    query.fulltextMatches(fulltextQueryString);
+    query.limit(this.retrievalLimit);
+
+    return query;
+  },
+
+  getCollection: function GlodaIMSearcher_getCollection(
+      aListenerOverride, aData) {
+    if (aListenerOverride)
+      this.listener = aListenerOverride;
+
+    this.query = this.buildFulltextQuery();
+    this.collection = this.query.getCollection(this, aData);
+    this.completed = false;
+
+    return this.collection;
+  },
+
+  sortBy: '-dascore',
+
+  onItemsAdded: function GlodaIMSearcher_onItemsAdded(aItems, aCollection) {
+    let newScores = Gloda.scoreNounItems(
+      aItems,
+      {
+        terms: this.fulltextTerms,
+        stashedColumns: aCollection.stashedColumns
+      },
+      [scoreOffsets]);
+    if (this.scores)
+      this.scores = this.scores.concat(newScores);
+    else
+      this.scores = newScores;
+
+    if (this.listener)
+      this.listener.onItemsAdded(aItems, aCollection);
+  },
+  onItemsModified: function GlodaIMSearcher_onItemsModified(aItems,
+                                                            aCollection) {
+    if (this.listener)
+      this.listener.onItemsModified(aItems, aCollection);
+  },
+  onItemsRemoved: function GlodaIMSearcher_onItemsRemoved(aItems,
+                                                          aCollection) {
+    if (this.listener)
+      this.listener.onItemsRemoved(aItems, aCollection);
+  },
+  onQueryCompleted: function GlodaIMSearcher_onQueryCompleted(aCollection) {
+    this.completed = true;
+    if (this.listener)
+      this.listener.onQueryCompleted(aCollection);
+  }
+};
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..14ea2967f031b5871c516fbb9491ab2ba247f786
GIT binary patch
literal 678
zc$@*I0$KfuP)<h;3K|Lk000e1NJLTq000jF000jN1^@s6JJyv-00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10wYO8K~yM_m6OkF(s3NdU;fxv{WMoNr;H#l3&BN^dJ1;)64IIx
z1a<YYLx0IG-Mj=HIz(G#3nj)H1cSUNQClOS#Tp~0)6sN({koj*4qx{p*kunqmk;mf
z^L}_f&&MJn{D)k00=NytfDlju3c!03#djx1El1*9)DNUlD#?f!lmg8GMh99t)N9bz
zGr$yyV&zzzi~6zfbyau!!sA!KYB4Vqpx5Vtu$uotc(7STxK5&2vGyIL4UgY9{kly2
z!2&bc4Z6|;8+4_|Om2htqXnj4l`)3=J|Jyw&PFCsYL0LS&fVfe{vw&oEH}rtNj>!O
z^m&!f-$zI$?-P&yhQ=pYD}kZkMY55!EKiD5QzG3>2?PQX2n0m-7e)T0MB0ld&NfBX
zQzC^)k!&Pw1!5@0ncz|YF*^`KbC}_@=b4>(0T|HvXa;30G5hivSe($DH^rI*AY=zh
z7@Zh)1?@c1;5VR6^VcqWTRMY2o#>?shznW`b4jNIP_hFBTKbXo{brq|Cts+n)bU&%
zV&`WAe|VVj$2ZWN1AT`&K*3DgrRKOA0-J@Ew`&Xs&*LBSV6|E4b$e8ncc`!Ja_vqK
z4g}WCv=2-22H$9M-|eNd_ZRo5Ixb1SD9tS$hid>$uR*<>AyK?{SduBW%T=vW!|ioJ
z7n28l_Q36RnZliN6_`4`+fJ3_g4cWw&0&Vq)?v2>#%bL?i8K7Fe`OsCb246##{d8T
M07*qoM6N<$f^)Yeng9R*
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..46cb432cd74541585dcb6a7a6158102139f109ef
GIT binary patch
literal 691
zc$@*V0!;mhP)<h;3K|Lk000e1NJLTq000jF000jN1^@s6JJyv-00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10x(HLK~yM_m6P2|lVKRffA2ord}le>+6P#W2hKhWa*H6+8Xd$K
zXciW95*^lG5_B9p5yDQSq_j|m2c<F4KK3riib-UlgH7E^H@CfOdwn{1)b!8`?vsb>
zdhYwVp6gbG5bVQbun&j;As_%O07)QDRC~3Ts_cl%&(HdXUl+zp4fFZ3$ws)hjd1TV
zre~IzPGoS18iy>#`#V=$*Bm!K<^GE&gI*Lq&u_MP+Rq)w(PEWX3t&rDxm0H{^^>mF
z%~b?vh-xdQ?FnM|{GP6G2M(tTELJeHr>r<yU36XS0Jq-*#AN4*?mkrYB_{*mxPX>E
z1?U7jXP}+|r48--Wl-z{+rL6_7Je*%Ug@_05stXQq(Jjk25<HNu(&V;#t7JY7`%0N
z`z!&aIdHlmw-hk}AykzEp;-k8S%3gdHfc_EiqU5yfDW}8X_lT~^yy<z>`<DL;wBqF
zKoJw&9|?4ZXi=p(mcu|5M17CjRuIilFM<J-EAT6wGyzG<6-hF{sH{VyE=dMtQ+YBU
zGSDbWjsU6^fTRhCQ^<oMU<=0Yzoocb=5Mji+@pDBWAh}3(`Zl9+mi^`$OFW8Lh=HS
ze*mAKcSB#ueJjw=8ypRJ3En<I^^eY*he`S$T!OVM6tZJPwd?y5hTHGK<6}9shQ(&a
zf7&Ix2{38~=Ji$ho!w2?Znl{WdVv^rbpWSZen*oHYBl-BjXda;G2k9iZDrqL?BW1h
Z^$#-X^g1^pC-?vW002ovPDHLkV1kq7B;)`9
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..cf0096f553ef97a6ddf0e57dbfda458f832097e5
GIT binary patch
literal 775
zc$@(T1Ni)jP)<h;3K|Lk000e1NJLTq000jF000jN1^@s6JJyv-00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10)$CKK~yM_os(TilW`QrfA2n>b8F1GO%v0QnG)ifLX-}L!b%A2
z1H_A<E{YJc+b+83D!QvK$_gYTOo$RfGa~vx7=;_81)9N}^DUijwh#BZ>E^n4Y3XeT
zo|}jNb2yyy`wO{TjvY1XItd&H8i5+%JKzPz=<vPV{#3|^M_sl+Xrq77lQ@x9>u7WD
zrnRMl4<CcPAODFqvrhLR!>dZ0<^mnQ(7*7g%a+;7P2GN%vR>?I!njK#H_QQBvf{A~
z9`;Re^+>_8N+HzK;R^{s<+E#h2P{?A7D|di0SI#T%hw1Eb;WXn9FPT8BZPy?<7aRB
z&k2X?ik{BLO<i`o9i>u9B9X`|l}h1oI4~FtB$LU!AB{$7s@4ptn(Opsu^AI?w;KRa
z6cI&{g@pwqDT&kRq^hba?~9^HJRYaHPG6?za+}S8fFD5+$Y!&7B@zkBN(=D&{X6_j
zCPOB(L6_TXR`&H&+)~&}wEcshP$(2glEm!nEP7p<k)d`TJ^sw%QWT|9$^860b8~at
zxKhs<r%ET}Mh;9+v#;^BC(QFV0ajMixEd_<UfPebNKHJp$(;w^@J)s(F=;t{w31^7
zEfARTssJxZsj<DS9LujHjpu9SjU#W@Sg7WDuM@K19hk<|S`NQg0lW%ej7S&(0du7h
z&)rF)fi?LoS^37%;2NHLlbH7y5fC621{nJb$qTHdo!Cp6eH+C0WEzcLjiuT|_(zlt
zX%km>EsoX-SXqR~;xHY)Q+dJ=n8H$O#cC(LmLW6~A+aQ3uoYsfDMqVT!O9Y>E-sTp
zCTu&~z<mk|J26>6R|py{q|>l2$)l#EVc_ykw*T{2{Q(tPHU&q1EGPf~002ovPDHLk
FV1gA!RG9z(
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..9ebf7d8988ba0df51b9859d15ee1ffe8d236bc37
GIT binary patch
literal 685
zc$@*P0#f~nP)<h;3K|Lk000e1NJLTq000jF000jN1^@s6JJyv-00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10xC&FK~yM_m6J_KR8bg)pL-o<?2=+S-I+@wO@Bs^i<!Z|jYt&4
zfIlsQ_AUzAv~ky}Hg2L#%(6`oB_xGnf?{NU!puZw)VQdOF=s|cveEl9>U=G(I!bMP
z;cO1yd(O*yo}-8edo&#B1TFzlpczO531Ea+^7-yjWyd@m2>@|4t%t*Y&@}M+z;<BG
zgu)V7<{)sLSTa{J4@UwhOcOM8js!tf+yezDZ>>#%Y!((5@(3-&k~wuNL7bqWOQS*R
zi$nkY8OC1ELlum@nWsNKL+Y!81Pt&SIuLhn9twA&Y0t{1PjTyRj+%xx&b7AC(VB$y
z3@D9Eq#{gBO;cQa%k3Kh)bF!kn_b9II4+;MMe@BO8yN|OLJ|svL`vf#zk5Zj@m+fv
zksrMxiEfdha9jnVXd3&{0SzGP0h;mp+;Xf@2A(_uY;az>McI=KJbpkysfOaHYxep8
znms@o+lk`Qz<NjI_-DYPIJd%T*5sgW5^l<K{?JV><ldtQ;5Yzj50GHZ+=*T)7MQ&E
zk=#sy`eU^$fBQqO>mn0ZPw=XAl6G|#EE6E%0Y)e+U8BAMP?&x3lQXfSbo7N$t5nJx
zW$xtL;1_(@Y@)p`4J%6kBioQXMK^j%HGZ7cb!rT)0*OGeT+PF|vs|f7(O_qwurNq0
zd3i5kenUqOf}&KKm2J`l7PGKiA?#natF<2L{O)sjeQr6H=|;8x$M$aR<5&F!2}25J
TirF(300000NkvXXu0mjf*S{k=
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..d80fc10827e5cbcae4c195e26caaf236a8c0670d
GIT binary patch
literal 676
zc$@*G0$crwP)<h;3K|Lk000e1NJLTq000jF000jN1^@s6JJyv-00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10wGC6K~yM_mD9~@TxApn@ZX)z+)1u&Xz9(zlvojjh>$=#DJZmH
z$ZCi*M67PyxYIwvo&SRy!HrOAX-h)Wtkg;cKQe=utU{?^KQhy1YKS^BcP5#+E|Rum
zanlFR;=Jd1&f<NZ6QpVCKUglFM+wuIzy``#F;`jYrv^{O%f(TAE{YZm$3zifo}^8q
z$>Tb$<~4j`uJZ7ic)2(#ux6|<vbf&1`di)1*%M~Zp7720o4)&D)0wH*{8XN;>LaNs
zbCrir#8*4<=)$L8Juv^yq_@rt3G9-hN7~K#vHX|C8|yy%a9nP?cFkPn-5|Yw`n)Jw
z^u~lv_@?zclSmX_zsc@>f^n7GZxalde0g2i`iVOmB+dCON`_-HfpDng!u#jI?;Bsx
zy-5C?3m<%R@OF#MpE(*+yIaa&S`>L93`Q`W#e^`=g9gcx%U@qaLi;8?B)43?^d-Rn
z_m+6nARd5;AiaKi)zHal-&P6m-YE(J>9Y^h%TY_Y^6P#-Qy{rp&Y-N>+9#&y?69{>
zS6}VY-r1i+(t>gpD{_sGm0q>C*VQ{53cClSCkuOYj)XF^CuOsTXx}HX^3-P|g#}{;
zfBaHYU8_4hI_Rb2gBtage`+m-$=EB!F}A8at{(W@pRl+vVr0x-r)TT-w(Wa=J2rmA
z@N36pa~Za3{P}#s`m;R{EjSwQ+Y#nzw`n!msgwL)w)^n{SM@K6Mf76eEEe4W0000<
KMNUMnLSTZEz(E-R
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b0d4196537da459df1ff0176d832f92fdc0b8b5f
GIT binary patch
literal 672
zc$@*C0$=@!P)<h;3K|Lk000e1NJLTq000jF000jN1^@s6JJyv-00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10v$<2K~yM_m6P2|lVKRffA4(v^;SA+a}6sZikU^xHV+aElOS}U
z7+KI+9z+pE=h6SrSrJiZNeMG!Qv@+X+90=sq{v~4jAYHNXcM>TKJE_Q%1K0c!E^F(
z|1O^E=6VznVT(fE9-s^G0d61;M1e8<u~)UD${#r7buOhgo;(Y$cCQ<)oITS-NBaS$
z-XwTEy^OhB;7Z%B(SuIY0RC8N6ApQu<*LlypDZ?ByV**8gGo0OpsJr-c7sO`XSj3D
zvZ5N46Zm7P-*5nztB?IIQ0svLNT~)kf>@!HgJcrY$zlAl3yK6iJ!skpEpFJ+Mr`gF
zP{i9j1*I>b>?1bc0g46hp^uOY!$KU2`Cb+1!eIx40@m~NUup+nG5#1TgJ5o_|MJye
zchj&M0h=AZCb|rO4^3lRvT6W66>wuR>-h@f480fxim*PV=Pgb%^!zC(7FZqEVUroa
ztw_)_gS*+sP7Uh#|H3N3S{|0>qACz&E$>6q7`^*}rnZa!eC_hvFx~f#Lm>|kRe>?S
zWv;V(4||UqvA3L}>2&AcU}Ta?W(2AN*$luKB7y`x0eR<_^1^^j-0qZYVo_?9O)Sd9
ztxhR_8W4&2MS`Ayh-@|tg<0&b#&W%b_b)$_O*^n1Y30ZK3=Q@)CwmX0=F;#zxq?vB
zFwhO)0T!(fn;ong)jA`Fa!Jpa%|J0f4BXh-?OJW)SN#Oi0R^pzo^-$f0000<MNUMn
GLSTY^iy@={
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a7e1bd066dc56b8394bd3377bcc0f622c56c8028
GIT binary patch
literal 461
zc$@*v0W$uHP)<h;3K|Lk000e1NJLTq000jF000jN1^@s6JJyv-00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10ZK_kK~yM_ozuN)ltC26@!z-!8?v8**x1;pjS_o13-3TI1VOa%
z5(FzP`~t5)tgI|eBPbSD-hf~y8lsR0DsDEfO7^jEH*D5-gE(;D%rnpboH+wilK9`X
zFiA=&r~BM#aCpSx7j7kaQc7traRbeYO*~Ap0mhxrE;D$*=ml9NM;J7?-Dd3q+xN&t
zezsZ1(aDV)O$N_Nrs56h^6L?!Y5guc2YEe#kM$X51`hdNqrD>?Hj_Aj4@1h^ng?mu
zXhB71cjiT|9%oS1befGz441i|KT0IgW%U}>e&AzvT6{!z4~$-$ji+<KSER)^oY-Y1
z$+*CoE?e(sz%|lih;w`V)0-rkTt7KA{||DCttD>GdjEox=<w_^+aXt|I=n>rCm6jJ
z>*pD@_=J9~uTC5nSXtmJR_1xie!r@eawy5?dFQ_YX((NVf>X4W00000NkvXXu0mjf
DrcJ-@
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e7072732a52396dd3b73b5bb4a0a8e54f110db26
GIT binary patch
literal 613
zc$@)c0-F7aP)<h;3K|Lk000e1NJLTq000mG000jN1^@s6?Q>5r00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10pdwSK~yM_jg!A?6JZ#~Kku6u>w)5sUhe2_!NJO<gA@lr2g^wz
zic1k(;^5{a?bN}^zo4@qh)au-kW~<@&<0cx%pizM&ip#7Ce0ahkHcNOOR3@m-{s}~
zexB!f-{(;x!c<g?E5I_a2+RWwU=OI_)}I3?I1eE3M*RU)iv?g4!zeME2g88eaTo^B
zA3(PQ2ZL?ku5#<^`~t~xF9KAH1r+bFY?C<)w2Ghtgt5i~p2w)$Zp~cY$%MWR@tMoU
zg>Op1HXl6oIF@TM4nr<pu(-5j^Ws&LmpdL=tr`6cS-f-241Eng(Bq1dinD@YJmc&<
zWEE&4k<Omzp}NZ2j~mbfeAYCu%xpd?TgB!~EFHaATDL~8S@J({#{cZm=P^=ocID%$
zNUJPy*8<4Kx+C(nEF1TosdM>M7J0uaQgL=g1SJ*c8w(33$z_tP|7d|RFd9SS?RVVz
zDGf9j2Fc~LA$^vvBM6A+0}bra9~_<OZ=iz#@o=Gm8r@Fx&qqNHn?r&94nPfHGDusP
zb_vS{1&?l4IUKj2xDl}3gl=;iw|+ehh$b;_o0t|Tl{h7px$Qx(*+MvnTmO;(AlZc(
zqr~YvoQQWJ2%?_%J1~rPVPc*JOyWPbF&)1F*J}IA2}qs800000NkvXXu0mjfVvYvU
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..f375aefa737eb75545983547b5a9406834d5d7d3
GIT binary patch
literal 701
zc$@*f0z&<XP)<h;3K|Lk000e1NJLTq000mG000jN1^@s6?Q>5r00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10y;@VK~yM_m6KmcQ*ji>Klf_WUHpfvZI_Y#gM=cjW-mceFTyqu
zZV*8aJs1cgL=V065Jf@s*jv$qPvv8RGzX3Ts1-tPlu<A>t!Qo2x!bn9+sy6P!<}sl
zd+5O72Z!JHa}I~^k%SP`gx}o<cz_<D6NmsaKmcE83_yZI00Q1he*k{B<8xwtc;t1~
zTi9mh)Cm_Sk9Y85;w#UmqBLyeICrdhe3!%0FZn`O*Z@(_B@^IxJ2pyU_Rh1sePHM)
zEv*(4Du9xine;mM@4aEDw`sYlce<{m%O!MNn}A2Cs>Ti#WOK$EK(X=$)?9eouY(Sx
z@XGkzeJF~C%Ly_BC!+)>qfmzllOZ@2B{&rY31*uQZi+#ts~+&M-9}NswOca`kIVsZ
z;e01oFCXT{?J&a+&07E2eGFV|V@y9lQdi=0YaI_{pa(@^RnIfOm?1o&5q_yLw~%IG
zF>ThKYJ{I?%r9kVOviZDa+8SAzKJtLCGq%3hVJfWEOj+wLuG*!|H@xQh`h_<3$@5V
zgnXg$xmr+N6IEF#Q1%BhFvFU$b*F!Uat38{Ap-$clTZ|7zZ)dpr?wO)KBUOT4JZjn
zCjkNg6(87D4X4VN=@=i!mr&ZRG})~DT*+aia_sl)W@nEID=}D&jp7TPt_C=en8l{r
zu{#k0lAqEf7k*&d+lFmVtLd$kIHY3B2)giv5*0xC7J$1nDjIDzuqt3_Fc+3H%(3&v
jC@{Dwt_A$ZHCD%OSehT~Rvemx00000NkvXXu0mjfgIg&v
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c20601bbe2f51f977dac330f09c9afe53320e6f4
GIT binary patch
literal 698
zc$@*c0!96aP)<h;3K|Lk000e1NJLTq000jF000jN1^@s6JJyv-00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10yjxSK~yM_os->5Q*ju_Kj+MCdStfg+KZ71h07QT9=k}gX>_v&
zhF$Km3nRMePl<wxpy;B!Y!sR|kwxH*JCL%<V?-k4ac;|(>DjC2*Tv?`qN{%Jy?J;(
z&+p-RzK<k?py5raqd*iG078HcB!OvS+MA=P(g8fB`j^w?XA`seYbARNBbQGwJk-U9
zk1Nc4`h~MvAU^1N+wFJUB&MYg;VIQ$wZy``_eJ0MoeQ)bbJPk7z^Z9`tIU(fbKJe!
zyxt^B=ZR_QefSl@;20-^SO}0nmZbyOstB+^77`c+WQ+tc?S>>K`$tif*PIKnnk^8&
zH3PuJ(-As-3ahIo@$nfDF!5l7&W;xNp+nKUE(1}HdoeNv)GG-hmjXnF0;rJ$pTFd4
zvB3b5iviSV0wYrZw-+EPiOK#sde08vQb;Xr5Dt652C1(bghL)usSUzC9@wkLPJ3Y2
zgrAE^Nlf-{)7R(1VFMc=*Tvzm*!xSBJ<#X#GN4l|QO;Ko6{|7XIwAq0YEj;;K&b?v
z%RrJ{6T8EP_FRYRKYpqg)L!b?oi-?#07)5`CYMD*qP@F~+57WsF7Gsen=3oaKAflh
zR2vc!Y-Ita5BKZ~ju8m5^lk<1#Wx(ScD!fYWWR4xHY=#{UV4VRAY(vof6sM?A+dlr
z=))JFv{NO$m?OVt;_@r_LmfC<n;^3Wzm0VSM;uo7Ht>jMWem3$S`=_NYr`p+wN{H}
g0=V7i?f<;0KdqV*b^1q4dH?_b07*qoM6N<$f-d?q*#H0l
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..9903039dfdcc35c2f06d312ee061a1ad4445e72e
GIT binary patch
literal 728
zc$@*)0w?{6P)<h;3K|Lk000e1NJLTq000jF000jN1^@s6JJyv-00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10#!*wK~yM_os(TilTjSTfA70F^<9b7e03sqwN^J~Q!j*qKty6}
ze$<6vMG-;LO&?%4LRKhr5rs$#q7T#{5)y3^l|fJ!UN|bdh$TUWu(qk_hOg6k-qXcw
zy4(KnTs-i9IEUx_jv}QbXU41jz%if(a06jr3V4k-G`4+J$%e<POXm|C51&u2_)JZs
zv3@V^{UwCI$9Xd`i@IviR%d_ZD6*c%8;Wj)$E!<)l+$<Lr}R_j4q>%fSYH=7b&W{G
zz-G0uu`U>V`kBiO1u=`FRN@Uqt(!LpSjx-wwlhUM8eV2;IgP*3j?<~}b8eNfw<{Fy
z%;R)>6&4aY@kjt@Rb<fPN7F_>EbZdsw2MnuuA^xhf%~^fC*P5`qk)c#*Dwr&dv|Vd
z<iHF^9gC1Mnvg+HKz?hMU7bEDgpk{3Xz+247edIct3D~GnxzbS0xD30rlDALR8<9_
zySp2J_LFCGysD}wc{&to05vM$#+HxVh+&#$&hoRNCpq3UO=LO-wtN7$3WQ0mQD3Lh
zbE}hNGKmm^{=Qyfvjg<^^&*5InM~4ivy=K-71q`O!io%f0#xj|K#7C7=n4ZP36>IR
z{0(*v*Y4)i<N{+8n{E4ws3^6=!aPKOKghIQsEy)sgGB*_i~!`O%xw}9euW`rH2s6*
zCAzbPGAAhTWq5|KFMeb(Ja}RsRmWW*fOrHJA|rT1ty#iQn8xAM(aS(lvcav>CB*;0
zVkCy75^pG)!!{70Kx?7ck$DbVex{sN1D59@WsCqFxorREulftSF($p>ywR2b0000<
KMNUMnLSTYTyFN7l
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..3de53a97d6fece2c00dae657f9e944fcfd0b6e6d
GIT binary patch
literal 556
zc$@(&0@MA8P)<h;3K|Lk000e1NJLTq000jF000jN1^@s6JJyv-00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10jWtuK~yM_m6Jhh6Hyd}zxNsw`=AhTGMS<711>ByS%`GeZ5W88
zpxdtdBMOB=p}!!T*|-un7NsFuL9m-7P<OL%J(DI8R60poGp~!6m`RO-2hQek&%K9x
z&l6f}me4HMfGSV{N<aX#ffiopL%LM_7B|a9pn+0#3Kl2@*&K`~a2>;N0HgQ}IKb=l
z7UE{Ph~Nv3YqRNq6sDmDk{PEOq6qro6`H4bonB_<prMPdef&1!LH3GilCbm4VfVSq
z$Il_3E+R5ga(k0-_{z1#AZ!48LZ5HfQ0hHTN{|sCF(f@T#a|k}1~86aNT5o=GFHV*
zxHhB2ye&~c-U6sfpn_6ndyjlX-bZZs9G!FlI6CbjY#-tJh{wKhw5|XuLZ5H{U~6lG
z?Al`5zidx|+bIN>KP3<_o-7K}#pySl;$#94NTAJiyn4dlnv7zAwgg%X2j-5e8r&Bq
z8omzzTJw;+!*=U9E(m!2vdMkyzwu17eh9<x46n1doG{C^u^n@cIdp1bKZ0+Ig#DTA
uhpJBAGUv$V%yLGtu{vIr?KJ-5SKR@9KhV~4hp+qq0000<MNUMnLSTY~1?=Gf
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..8c579f47e383d63d755a09251eca17ee7a8fabb3
GIT binary patch
literal 682
zc$@*M0#*HqP)<h;3K|Lk000e1NJLTq000jF000jN1^@s6JJyv-00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10w+mCK~yM_m6Op+Q*j){KX;pVySG*{ubZ;y!4V71r0$-gLBb+r
zWEfbF;ft_`{saGr-h2sqD3B5|BMGSoX^=0wgr(3(rLr++o3YK^-MQ`F9`4l)eDT0}
z`SLq_59fEjf@N9!hXr*M7y)8H6vzS@;4Mk*_0gxop?E<J0n^Cx7~KlUGKgL<8c@@r
zRD`-d2c$@9g@57&HH5%tE7TkO^rwS&A9AQe3W<1__e(jnl_EE<hq=<#L@4HWu((W8
zE4Um7(`K+IIQ1;Y`c{j{i9r&>VQ@oYILzdOLDsigOg+tE26}?Pw7vPw)lp=5p8hDD
zx`y_305DK5E<y7b2<Ong-2lOd+P4OkkFb#iL%-tzM(9?+C4h8`@q2LqHnWeRJp<l2
z<M$_=SsJR#(5b-o&k+|8LzX!aBpD#)0iuXruz>m^voB@<0~$+U0!EtI=TAWJL3Pm<
zi(Y`J2gqVHVt8bzFQZ1*fI8J5W%jmpP6u_=^98UZs8#GGjRrv017xV_ht~J2CDM;p
zD6EzU^qpaMvqGr9i}=GKL|=#FPzT7^X-o1v7oy-6SbeoYSL7_AfdDSI3$tZX__9lB
zqs*n-5olZR-A?<UBrgf}j_vyu8hcIry-t9ZLldgoI$o&*Q7=L%KSxr#dr*=Tzw<j{
zrHEgVpoPu0LlgXpWD9rmJ3#9AZu{ghIu-jnM6VrAU59cJjN`g}6eqZ<zo`xCXRRGu
Q4*&oF07*qoM6N<$g3oL#I{*Lx
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..65ee06bf7b41197695bcf0fde9c27ad46e3e822e
GIT binary patch
literal 671
zc$@*B0$}}#P)<h;3K|Lk000e1NJLTq000jF000jN1^@s6JJyv-00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10vt(1K~yM_m6J<rQ&AL!zk8eJajjA$4+0iS9VA3_(3?RJO4W&m
zNK1t(IMEC<aN<v~gAQy-M-~kj8xle$S_o*t+=(wz#8OnaX;O<#Z63|zI5_DeIuJK(
zIBeG1`>eCqQG^imMMj_6`q`d7Xm#}wmv!u0<zT})s2ZMO%L)-AbrPx^ku$pI)w{hX
ziM3zXM$ez+=8aJT0XLsMlu73{+1%J+HZ=6|>ZQ|ni5OeQ<c#iVw#EDTeAD&dVTgf2
zRW=krS7zs+%iN<iVw1Lt&7xc+Vr(6fpX2q;FyaNv8K4fLb%Hzy!U3%+m?l)rBoX75
zBGThiXxa(^ADp_3QM>>&(EY2>`VPuDjL%m<v7?WDgz9VfvJQ>fv;_#`a)PRW<2tjq
zCji)3Ux3as*e0006YbUVu=fUrolyE3R)HXz#(#xF0|;6GA6A>xSI?1nwhT0&xhnNG
z@+6+dL9xSLPLi!Q0G}e#<8Sf#gA8dfz`q}?1MJsevsh4p0{b-y>L3B_mOLM23))qQ
z5kS2LP*8y^KenMGjvI7f>2Z<C&<mCpi$}YbHxaPA4Ukod7%A$tB&AZ{wD>}ia=A`?
zu_$YYjVmQ+)RIJuRL@GpsF-C;6WRjW5WVlm?RF5o=kE~(R7}`0%RuZeZv*q#wHbz;
zavfG1G@DZ6?zWr=v3_r#jEp`7gn=O71J;28kp1&l{RRQ%0L)Z+7V`iA002ovPDHLk
FV1ihKBAx&M
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..f0622a02c3320f161010a868ea1524df7bb2e796
GIT binary patch
literal 700
zc$@*e0z>_YP)<h;3K|Lk000e1NJLTq000jF000jN1^@s6JJyv-00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10y#-UK~yM_m6P8~Q(+v&Kks(->&%?yI(kwe7c?nWv5kUA5V{Ci
zXciWB6J7RS1pN)+O%UBAB`Ad}qzi>X5p4xgq=++|HO&%r+u2#i?d{^sUpGMyJU1`z
z^S<Bj^L@TTO348vWBo<5c4ecueXb+u4gqsO8hAw_^J4#1u_Io4az=lUTzgrrInR0m
zeuf5)6N$7kJ+s7gY8k(*a7d}XyM3wUs{i`Ol|6AX){5X$PBdwq(JpWZ`)LW(Y?cZZ
z*$<0E0>z(5PLs&2I_edS(57ph@esHJpt!)vKDzJ+T8Lac0YO~@M(m#_qy4Drb54fA
ze}T;GDZnJwJp<M<hz>IIy&$~ALSJC(4J^!oX$&YpA4h_AslLk$-RJ=zJNFQ(qu}Xb
z=;odJSqchop(zL}OMMO?j;ius@TmZC1qkEz*yfa{7<)Dfm{6Iq%`#JrJ$(ei3xz3L
z?DhbJg`AAOC)^#ULAA|M8i6uMYoFULkUp@spbC@>_%@qXfHWl|j;iu`e1YHDO+vlx
zv_~}@il~dNI_u*r8vtqNy<55y%NnM+h2~?8)T)yC*E!OUzf#B>xP2}f4*P781Zomi
zbFgjQ-wDY$nm$OI4gwHhteY&RevnOl=TFfhI^0dy`J=Fwhu`@L5}9iU5=Kzh(Aq$V
zJ!d6s<ci2@jb?8N*7Lgw+s!t~SSv7sR~@7&Xph6~fl9^haq}0L#sqMeL}vBCV(cOr
i>j(OPI1uI^TlE*WJ?Uo&e~^3t0000<MNUMnLSTZynJ={f
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2825b9659f67197201c39ed1d6a2fbb376bf6da3
GIT binary patch
literal 693
zc$@*X0!safP)<h;3K|Lk000e1NJLTq000jF000jN1^@s6JJyv-00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10y0TNK~yM_m6P2|lVKRffA70@U+<O<opVc>xu8j3lv@Rf(vHGL
zhJpp1byoBTc@jhiAvMYphj<WJf)#C{Nu-3cWNTY)mafgswLKlYx=qiz;68bHuKVM<
zuUisAZ~$T75HJY%0dG9B8*4eDyjP8Km|%3W@vF2?&ObZveDo^&BKKE!DK8rd40IA0
zaI>_SCN#N9X)BLeS6R3pY`fNWdofujhkflxd|LWrb$BjxW3lSB)B>pPMMbc_u!dXr
z`zM041fywruY)n1F2|X`2`si&Q1qaqA@rbc#(mBMc9#Pf(<)E(4xy?qIq3z<WunWc
zfFi!0St!Rr>LmL03P>h=p3ks7534IsEZk6lLE7vfOJKgr=*@lrVk-}zG7iRmMsMBO
zdz*vYTd>(7wLT~VepHqJf>{OdD}Wb+Q4?ophS1Y-pa`W|O{n4=p(hVPGC^)eBO8nW
zUP(;#e!$z~$Es@LC{4f)h;oD4dJtwPZ$kyh7vS4+L;)h?3-#8;Hb~S~RoxXNB5`7G
z60{y`=JfUBuu}ktD8Mw?%w1Yq0ZFR{q`zzud!69>yA&;5O?dh`=(^~Hs(`Hw!1TUJ
zp5t&0<8-k$|AUW@moZrMc=|i&?mLQVGigaHz(x|X$q9ndkpm9H?sEJpS&(EITc@?|
zGJ#fcBL$nun!{?|ChThm#xSYF*zDRm3`QuGv>CTDP%KOU_XtMQb#FJ~&{Y8|Km?dR
bd{zGdu&3Tdg8MhQ00000NkvXXu0mjfv!p8j
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..074d1cc0167b828f2e1a8465f8ba8037e9f177f2
GIT binary patch
literal 682
zc$@*M0#*HqP)<h;3K|Lk000e1NJLTq000jF000jN1^@s6JJyv-00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10w+mCK~yM_m6Jhh6Hyd}zn5lak_nBW#-u^5MZ{DE7fsw0A`t|s
zfl7obbf*hJ-1t+(g^Gx}P>U8UN^2;HMhbD!%%TOcu_z@dX_{2hq)sN8wBuq@n=V}R
zz}-CF_u$-fp0F&7U7C)J0K-5O2m?7F3#3SBZ#%DwfADl9xLj;LevzqNY)Ed-ojpiA
z)<^nNo{zJ?P+E1yhPvPN2j%M|wBimt9SOF!tc5!tjKGasXYlTm?FTJj%bx0blZOxH
zxP9KW>U4<HB(&l-{F+cG$>9(<y+9o-vjdjEa)VigLIFyJNfO#+VNDH=psH^;83xY~
z+Ttm|ATp2!^CyTyXiISrE+R+2Ks5v3b71I~oWL*#{NNDazQE{}7yv(Vk6?2glo+E|
zueHx+q4pVkepty5JAf#v%AVv_0isSIjI7xH>N8Bd8V3w$q;0>(EE6xEfN()=#)f4D
zAS|q@!8yVM(Wa1)JY5LI_uqwCMg>#fM7B&=Ud%dxEPr$)xeM)S4w_YHnXpO6m|#}X
zp68I1E~x7OSqG3}t&Bj>)9)p7cb>}f#tygzmHY;od-L=h@gfkgUIs|*<Qxbk=?(Gi
zeID)ES7eWb|Co>RQiY~&5*a(e@pvDU3b3}FbGu;(7Vw7x1bS&~v?wmFQCrdJ4ypvh
z`%yejD6PQn!YURW4QqEBxQ|Os;`7^a$O<$Xw#W4{82Th|b9c8pwTEBz7gy)}RAL@e
QkpKVy07*qoM6N<$g7~>3YybcN
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..40d84ea7d62ab5b6b9c7ac650566274db6745cef
GIT binary patch
literal 669
zc$@*90%HA%P)<h;3K|Lk000e1NJLTq000jF000jN1^@s6JJyv-00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10vbs~K~yM_m6P8~Q*ju^Kj&<kejzvNoVn9(#FQ{1N#{jH7kVQ$
zFogaBuhvCD&|L`iFDS7J3cBbfO8cShMuILxNF|KM8rm+Tk+vyr>fF&C-!3NZq;=5)
z&&`+TdHFss&#Q?De_*1sALs+Zz!@L~%mQ&Db8p>J?Z7<I*$Bi?Y5>y!rJ(j0lx)~4
z!fFn-i(|kIB6BNM^F(JO8ea%n0YX90waP#N_V(!rEH6X$=P!iwMCMlX{U5{#T7j9I
zmvm+m2LTTUbVNx1*koqK3x*W{VgR0dUGMr)>b^8LG5>9i<ia}7UoE10HJY0mnEaUG
z;j<(q+X0}Z?G)NZp^fB&&pObDX`p#Dy02K6W*zpfL-h6(>l-^n?@VFuIG9Eq-Ipyi
zT?4-X(5C}olmcM*QG$`j30hD4h>!K+_tg@A(MxNykC7({hMy)XA?p=DSd;ON0`2X`
zsr6Jc>ifjKtV7&&AT__C11U;&Rp=JG-^4}R21t2<S+<H{l;Zu}uY@jL=U>Q7z2xf9
zIoK`&%zA-1tGOsAP7-XZ!8m(?(8Zpk#_10@xi_#Ukk0|c5fNm(V^lsxr2KtQCU5mf
zekm<(<d@Pid9z2#$w86nh{$-ysE8bDm|+FV4JPxi7s+Q${H<NAFU;UGvb0}o!CT0}
zTD4(EyIrRS@EcIC*z?q&mv<`}^EoK}t=n$=$5s6Xo@NFfFvU_Q00000NkvXXu0mjf
DlN~6{
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..db62c654ebac741cf436a325a5c151499bacd5ad
GIT binary patch
literal 625
zc$@)o0*?KOP)<h;3K|Lk000e1NJLTq000jF000jN1^@s6JJyv-00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10q#jeK~yM_m6OeD6Hye!e{W3lF={Ou8-pK+n-m2XowzAPDhLt-
zl?YXEBf1cS8~+q{iiqOIpaqN47`o9&pe~x(2ttD>r6^-QDkPzkNoSJh;w5b=i1fmN
z$K{;IIq%*lq?GKaoG}9=fjAHY%0LOolQQ29Q^l4zXGB&jn@?XXwJvu?LR>h1l<C9?
z3Ln?_Q2a^2Yjb02|GSeB|7}ucWm}vxB3@5^e(>HIzjN;#qX+!zLJ9N~nvG2!KU(Jg
z#bDK^iL<24%0T>@Xf)07DELN!Hb{3!JOVNVZWAmEYSug{^Qw>wlQZc08&1by?-b_B
z8Nk7qD1iF|#4*gT(;$Kvr@laQ3BHxVv9I`mB!|PG2?$+c_F4jf@8u`Z%|al-?Dbpy
zUJ+WKU@Q#3){+_!N7va^g>-<p4~XFpsC?~3=3Zq12Ra3n*C{gh@)?LAv=)`v9{`96
zxiGm*Y$DD+9rp152kQbGHmt6cG@!(Wtw>!I!E@Dl3odv~#gRbU1}OP}JnQwl96SUF
zo~LvFjCwPhf}Vs%9U#9w=kaKoBT*1243=bIUG`nnELc}_*7~#zHLHqcfhWO3VPLxl
zbfpshwyHn|{@QKeAwfOOSXhn2AAn9rWo*>JvFCxz&fOlaUHqy)^$_KvNQDCd00000
LNkvXXu0mjfgfIsT
new file mode 100644
--- /dev/null
+++ b/mail/components/im/smileys/theme.js
@@ -0,0 +1,25 @@
+{
+  "smileys": [
+    {"filename": "smile.png", "textCodes": [":-)", ":)", "(-:", "(:"]},
+    {"filename": "grin.png", "textCodes": [":-D", ":D"]},
+    {"filename": "wink.png", "textCodes": [";-)", ";)"]},
+    {"filename": "cry.png", "textCodes": [":'("]},
+    {"filename": "shocked.png", "textCodes": [":-o", ":-O"]},
+    {"filename": "confused.png", "textCodes": [":-S", ":S", ":-s", ":s"]},
+    {"filename": "slant.png", "textCodes": [":-/"]},
+    {"filename": "slant2.png", "textCodes": [":-\\"], "hidden": true},
+    {"filename": "angry.png", "textCodes": ["x-("]},
+    {"filename": "sad.png", "textCodes": [":-(", ":(", ")-:", "):"]},
+    {"filename": "cool.png", "textCodes": ["B-)", "8-)"]},
+    {"filename": "tongue.png", "textCodes": [":-P", ":P", ":-p", ":p"]},
+    {"filename": "embarrassed.png", "textCodes": [":-]", ":]"]},
+    {"filename": "heart.png", "textCodes": ["<3"]},
+    {"filename": "straight_face.png", "textCodes": [":-|"]},
+    {"filename": "manga_smile.png", "textCodes": ["^^"]},
+    {"filename": "manga_embarrassed.png", "textCodes": ["^^'"]},
+    {"filename": "manga_tired.png", "textCodes": ["-_-"]},
+    {"filename": "manga_annoyed.png", "textCodes": ["-_-'", "--'"]},
+    {"filename": "manga_stunned.png", "textCodes": ["o_o", "O_O"]},
+    {"filename": "sp_laugh.png", "textCodes": ["XD", "xD"]}
+  ]
+}
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..225b661df76c22499c28e1bea8091ca90be29708
GIT binary patch
literal 670
zc$@*A0%84$P)<h;3K|Lk000e1NJLTq000jF000jN1^@s6JJyv-00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10vkz0K~yM_m6P8~Q(+j#KWFa8*~yvBxwWzggC><-xOF2ejD%PN
zV}S+U)kQSA>8AgqyCS0Qk`fw)b9hsWp<TE`5Co1WZ6v3;O?1<<v!ADnvvkr$_=V@@
z<@vn4-{<?hg0AcAQ`|oRgn$6x12RAwND!9ab+!t-aNO@%&DEa2Tr6L!TI^iDbeyr^
zNs=G4d`PWfYgCvTb<CaeSnm*)b9->y?`gF3FOS}9?z{Id(sS5qT&M#rqe@#fo<3RP
z!MM0?HVGF9%elYs8@%2KC%j<p0V<%^J75dwcBq#?Q6R6x2+KDFeRgC5NqWm!9~>G*
zUOoqC_=l2E{{_MT^0zS%MErwap|l9!GoY#0%|M8L7nlUFUuE)U5P%<<XV9DmTad|H
zx7%kaD1U-p7yQnKOh5oh;y|)X00A@L!)i19Rpyy_Jq>73O&Wf+6f>`0fFMG7-hiz(
z0H2`Gjx6CD3eYV<7ylnv6WCE<bvbPU((I_vg7TV*BCkNBZZz#y9p&>1WkqejPz6Yv
zfds|EJ)C_w+!CAFGII|X5G_`^MUn08Dq6n6@RidzB7M-*VXFX;*vr}Njd0Wpf?;bT
zR-}+9;2Idjxv9dG1j81{D^N6YHnry$C9kZbDA3TM3C?jRXO4JrHrg%6Oa{22tOL=1
zb{lw%C`IUX8FBpVf`uBKuR(tvG&Kf9_wRP64)Ck~0QWcXUDTJU3;+NC07*qoM6N<$
Eg2tsL5dZ)H
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..556c2a5c72849e90fe4c34d4163174c08e72af06
GIT binary patch
literal 680
zc$@*K0$2TsP)<h;3K|Lk000e1NJLTq000jF000jN1^@s6JJyv-00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10wqaAK~yM_m6J_t6F~rmpPf#3(~Z#-l1;WYMS5tUU=JqrpeQ|f
zDS=7_5j+(1BHldo2YA&#kRpoWtq2yy8oX#FphB~VqJ>Z`SR-lDv^0(RYLi_LTeY!X
zeBos-^UOOR?<kJr(4qO*1TYT7fhdp#GC+!i^<@84<u^PZ3js-VeUe}RbRATUg;%S*
zOn*mf)wy)i_v~0my+Xn&{DJ3VArw9kHVux3K`8Em4q&_9F7I%AW|8Y>Jw+*$5fWBG
z?Cl^)*ffZkAQkB1FL!geH^B7tDP*Y60FrL<(-RZu`eTNpE+}x#UaHcMwLAujhuGjJ
z*nSOPve2lVlfXE^04>{L>goc^D`lcXe(uklK?(udX)av648Te@4V5?G55U^$xCG+p
zI#LR5-58}m;$`Jq1t|rz`L}v>fVc#rs2VuXZ+gLo!HCZVZJ?3n;p{z7JWzS=!m0)k
zl|Yt8Gmg|@?~-x$jdqUMz$`dD)K-h^R$CnL3xKQuGE{5WyEb3bc{lNZcX4*gyuJGo
zpW%gi4Im?c6dNT33aw_F#XC#1s_hOq|GvbFn--B+7$Jai2_W^WB#$u6Nx~+d7jk4C
zETMY4@kb7^mHR<$v&rbxFhgSlu%3sF{2U4Ed}qS~rhyTr)wIdID^TCsLGRV*J07H4
z>w@()*vuDk7$IR54tCq4PvQ@_&!KA2Y`SMGm!MIb1Fm&;d%ynUSN#M)^WGeH+@0(I
O0000<MNUMnLSTZ2t0%7j
new file mode 100644
--- /dev/null
+++ b/mail/components/im/themes/chat.css
@@ -0,0 +1,511 @@
+@import url("chrome://messenger/skin/imStatus.css");
+
+.im-placeholder-screen {
+  background-color: -moz-Dialog;
+  overflow: auto;
+}
+.im-placeholder-box {
+  background-color: -moz-Field;
+  color: -moz-FieldText;
+  border: 1px solid ThreeDShadow;
+  border-radius: 10px;
+  padding: 1.1em;
+  -moz-padding-start: 20px;
+  margin-left: 1em;
+  margin-right: 1em;
+}
+.im-placeholder-image {
+  list-style-image: url("chrome://global/skin/icons/information-64.png");
+  -moz-margin-end: 1.2em;
+}
+.im-placeholder-innerbox {
+  max-width: 25em;
+}
+.im-placeholder-title {
+  margin: 0 1em 0.6em 0;
+  font-size: 160%;
+  border-bottom: 1px solid ThreeDLightShadow
+}
+.im-placeholder-desc {
+  font-size: 110%;
+}
+
+#contactlistbox {
+  margin: 0 0;
+}
+
+imgroup {
+  font-weight: bold;
+}
+
+/* From instantbird/themes/blist.css */
+.contactStatusText,
+.convStatusText {
+  color: GrayText;
+}
+
+.contactDisplayName,
+.convDisplayName {
+  -moz-margin-end: 0;
+}
+
+.contactStatusText,
+.convStatusText {
+  -moz-margin-start: 0;
+}
+
+/* Avoid a strange jumping bug when hovering and the startChatBubble appears */
+.contact-vbox {
+  min-height: 40px;
+}
+
+.startChatBubble,
+.closeConversationButton {
+  margin: 0 3px;
+  padding: 0;
+  border: none;
+  background: transparent;
+  -moz-binding: url('chrome://global/content/bindings/button.xml#button-image');
+  -moz-appearance: none;
+  width: 16px;
+  height: 16px;
+  min-height: 16px;
+  min-width: 16px;
+}
+
+.startChatBubble {
+  list-style-image: url('chrome://chat/skin/prpl-generic/icon.png');
+}
+
+.closeConversationButton {
+  -moz-margin-end: 0;
+  -moz-stack-sizing: ignore;
+%ifdef UNIX_BUT_NOT_MAC
+  list-style-image: url("moz-icon://stock/gtk-close?size=menu");
+%else
+  list-style-image: url("chrome://global/skin/icons/close.png");
+  -moz-image-region: rect(0, 16px, 16px, 0);
+}
+.closeConversationButton:hover {
+  -moz-image-region: rect(0, 32px, 16px, 16px);
+}
+.closeConversationButton:hover:active {
+  -moz-image-region: rect(0, 48px, 16px, 32px);
+%endif
+}
+
+
+/* From instantbird/themes/conversation.css */
+.browser {
+  margin: 0 0;
+}
+
+.conv-bottom, .conv-nicklist {
+  margin: 0 0;
+}
+
+.conv-top {
+  min-height: 115px;
+}
+
+.conv-top-info {
+  margin: 0 0;
+  padding: 0 0;
+  border-style: none;
+  -moz-appearance: none;
+%ifdef XP_MACOSX
+  /* Copy of .main-header-area from pinstripe/mail/messageHeader.css */
+  color: #2E3436; /* Aluminium 6 */
+  background: -moz-linear-gradient(top, #909090, #FFFFFF 5px);
+  border-bottom:1px solid #BFBFBF;
+  padding: 0.6ex;
+}
+
+.conv-top-info:-moz-window-inactive {
+  background: -moz-linear-gradient(top, #CDCDCD, #FFFFFF 5px);
+%else
+  background-color: -moz-Dialog;
+%ifdef XP_WIN
+  background-image: -moz-linear-gradient(rgba(255,255,255,.5), rgba(255,255,255,0));
+%else
+  background-image: -moz-linear-gradient(rgba(255,255,255,.3), rgba(255,255,255,0));
+%endif
+%ifndef XP_WIN
+  border-bottom: 1px solid ThreeDShadow;
+%else
+  border-bottom: none;
+%endif
+%endif
+}
+
+.userIcon {
+  border: 2px solid rgba(0,0,0,0.15);
+  border-radius: 5px;
+  max-width: 48px;
+  max-height: 48px;
+  width: 48px;
+  height: 48px;
+}
+
+.userIcon:not([src]) {
+  display: none;
+}
+
+.userIcon[src=""] {
+  background-image: url("chrome://messenger/skin/userIcon.png");
+  background-size: contain;
+  background-repeat: no-repeat;
+}
+
+.statusTypeIcon {
+  margin: 0 0;
+  width: 16px;
+  height: 16px;
+  min-height: 16px;
+  min-width: 16px;
+  -moz-appearance: none;
+  background-color: transparent;
+  border: none;
+}
+
+.statusTypeIcon[status="unknown"] {
+  list-style-image: url('chrome://chat/skin/unknown-16.png');
+}
+
+.statusTypeIcon[status="chat"] {
+  list-style-image: url('chrome://chat/skin/chat-16.png');
+}
+
+.statusTypeIcon[status="idle"] {
+  list-style-image: url('chrome://chat/skin/idle-16.png');
+}
+
+.statusTypeIcon[typing] {
+  list-style-image: url('chrome://chat/skin/typing-16.png');
+}
+
+.statusTypeIcon[typed] {
+  list-style-image: url('chrome://chat/skin/typed-16.png');
+}
+
+
+.statusMessage {
+  margin: 0 0 !important;
+%ifdef XP_MACOSX
+  min-height: 16px;
+%endif
+}
+
+.statusMessage[noTopic]:not([editing]),
+.statusMessageWithDash[noTopic] {
+  font-style: italic;
+}
+
+.status-overlay-icon[status="away"],
+.status-overlay-icon[status="unavailable"] {
+  list-style-image: url('chrome://chat/skin/away.png');
+}
+
+.status-overlay-icon[status="idle"] {
+  list-style-image: url('chrome://chat/skin/idle.png');
+}
+
+.status-overlay-icon[status="mobile"] {
+  list-style-image: url('chrome://chat/skin/mobile.png');
+}
+
+.status-overlay-icon[status="offline"] {
+  list-style-image: url('chrome://chat/skin/offline.png');
+}
+
+.status-overlay-icon[status="unknown"] {
+  list-style-image: url('chrome://chat/skin/unknown.png');
+}
+
+.statusImageStack,
+.displayNameAndstatusMessageStack {
+  margin: 2px 2px;
+}
+
+.statusMessage {
+  margin-top: 32px !important;
+}
+
+.statusMessage[editing] {
+%ifdef XP_MACOSX
+  margin: 29px 0 -1px -4px !important;
+%else
+%ifdef XP_WIN
+  margin: 28px 0 -1px -2px !important;
+%else
+  margin: 29px 0 -3px -4px !important;
+%endif
+%endif
+}
+
+.displayName {
+  font-size: large;
+  border-bottom: 1px solid rgba(0,0,0,0.25);
+  margin: 0 0 16px !important;
+  padding-right: 20px;
+}
+
+.prplIcon {
+  margin: 0 1px 16px -16px !important;
+}
+
+.statusTypeIcon {
+  margin: 32px 0 0;
+}
+
+.userIcon[src] + .statusTypeIcon {
+  margin-left: 32px;
+}
+
+.conv-messages {
+  min-width: 150px;
+%ifndef XP_MACOSX
+%ifndef XP_WIN
+  border-bottom: solid 1px GrayText;
+%else
+  border: 1px solid rgba(0, 0, 0, 0.25);
+  border-left: none;
+  border-right: none !important;
+%endif
+%endif
+}
+
+%ifndef XP_MACOSX
+.conv-messages[chat] {
+  border-right: solid 1px GrayText;
+}
+%endif
+
+.conv-textbox {
+  margin: 0 0;
+  padding: 0 0;
+  -moz-box-sizing: content-box;
+}
+%ifndef XP_MACOSX
+
+.conv-textbox > .textbox-input-box {
+  background: inherit;
+}
+%endif
+
+.textbox-textarea,
+notificationbox {
+  overflow-x: hidden;
+}
+
+%ifdef XP_MACOSX
+grippy {
+  display: none;
+}
+%endif
+
+.splitter {
+  margin: 0;
+  border-style: none;
+%ifdef XP_MACOSX
+  min-height: 2px;
+  background: -moz-linear-gradient(top, rgba(0,0,0,0.35), transparent);
+%else
+  height: 3px;
+  background: transparent;
+%endif
+}
+
+#conv-toolbar {
+  border-style: none;
+}
+
+#logList {
+  margin: 0 0;
+}
+
+.conv-nicklist > .listitem-iconic > .listcell-iconic > .listcell-label {
+  font-weight: bold;
+  -moz-padding-start: 1px;
+%ifdef XP_MACOSX
+  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.4);
+%endif
+}
+
+.conv-nicklist > .listitem-iconic > .listcell-iconic > .listcell-icon {
+  min-width: 16px;
+  margin: 0 2px;
+}
+
+.conv-logs-header-label {
+  -moz-appearance: treeheadercell;
+  margin: 0 -1px 0 0;
+  padding-left: 3px;
+}
+
+%ifdef XP_MACOSX
+.conv-nicklist-header {
+  -moz-appearance: treeheadercell;
+  margin-right: -1px;
+}
+
+%endif
+.conv-nicklist-header-label {
+%ifdef XP_MACOSX
+  -moz-margin-start: 3px !important;
+  margin-top: 1px !important;
+  margin-bottom: 0 !important;
+%else
+  font-weight: bold;
+  -moz-margin-start: 0 !important;
+%endif
+  -moz-margin-end: 2px !important;
+}
+
+%ifdef XP_MACOSX
+.conv-textbox {
+  padding: 3px;
+  border: none;
+  -moz-appearance: none;
+  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.7);
+}
+
+.conv-textbox[focused="true"] {
+  box-shadow: inset 0 0 2px 1px rgba(40, 120, 212, 0.7),
+                    0 0 4px 1px rgb(40, 120, 212),
+              inset 0 1px 2px rgba(0, 0, 0, 0.7);
+}
+
+.conv-nicklist, #logList {
+  -moz-appearance: none;
+  width: 250px;
+  border: 0px;
+}
+%endif
+%ifdef XP_WIN
+.splitter.conv-chat {
+  border-left: 1px solid rgba(0, 0, 0, 0.25);
+}
+
+/* Splitter style change above somehow made it smaller, fix this here: */
+grippy {
+  margin: 0 1px;
+}
+%endif
+
+.listitem-iconic[inactive] > .listcell-iconic > .listcell-icon {
+  opacity: 0.45;
+}
+
+.listitem-iconic[inactive][selected] > .listcell-iconic > .listcell-icon {
+  opacity: 0.7;
+}
+
+.listitem-iconic[inactive] > .listcell-iconic > .listcell-label {
+  color: GrayText !important;
+  font-weight: normal;
+}
+
+.listitem-iconic[inactive][selected] > .listcell-iconic > .listcell-label {
+%ifdef MACOSX
+  color: -moz-DialogText !important;
+%else
+  color: -moz-cellhighlighttext !important;
+%endif
+}
+
+.conv-nicklist:focus > .listitem-iconic[inactive][selected] > .listcell-iconic > .listcell-label {
+  color: HighlightText !important;
+}
+
+
+/* from instantbird/themes/blist.css */
+%ifdef XP_WIN
+imgroup .twisty {
+  padding-top: 1px;
+  width: 9px; /* The image's width is 9 pixels */
+  height: 9px;
+  -moz-margin-end: 3px;
+  -moz-margin-start: 3px;
+  background: url("chrome://global/skin/tree/twisty-open.png") no-repeat center;
+}
+
+imgroup[closed] .twisty {
+  background: url("chrome://global/skin/tree/twisty-clsd.png") no-repeat center;
+}
+%else
+%ifdef XP_MACOSX
+imgroup .twisty {
+  width: 9px;
+  height: 9px;
+  -moz-margin-end: 3px;
+  -moz-margin-start: 3px;
+  background: url("chrome://global/skin/arrow/arrow-dn-sharp.gif") no-repeat center;
+}
+
+imgroup[closed] .twisty {
+  background: url("chrome://global/skin/arrow/arrow-rit-sharp.gif") no-repeat center;
+}
+%else
+imgroup .twisty {
+  -moz-appearance: treetwistyopen;
+}
+
+imgroup[closed] .twisty {
+  -moz-appearance: treetwisty;
+}
+%endif
+%endif
+
+#searchResultConv > .prplBuddyIcon > .protoIcon {
+%ifdef XP_MACOSX
+  list-style-image: url("chrome://global/skin/icons/search-textbox.png");
+%else
+%ifdef XP_WIN
+  list-style-image: url(chrome://global/skin/icons/Search-glass.png);
+  -moz-image-region: rect(0, 16px, 16px, 0);
+%else
+  list-style-image: url(moz-icon://stock/gtk-find?size=menu);
+%endif
+%endif
+}
+
+#imStatusAvailable {
+  list-style-image: url('chrome://chat/skin/available-16.png');
+}
+
+#imStatusUnavailable {
+  list-style-image: url('chrome://chat/skin/away-16.png');
+}
+
+#imStatusOffline {
+  list-style-image: url('chrome://chat/skin/offline-16.png');
+}
+
+
+#statusTypeIcon {
+  margin: 0 0;
+  width: 16px;
+  height: 16px;
+  min-height: 16px;
+  min-width: 16px;
+  -moz-appearance: none;
+  background: transparent;
+  box-shadow: none;
+  border: none;
+}
+
+#statusTypeIcon dropmarker {
+  display: none;
+}
+
+%ifdef XP_WIN
+#statusTypeIcon .button-box {
+  padding: 0 0;
+  border: none;
+}
+%endif
+
+#statusMessage {
+  margin: 0 8px;
+}
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..aca3a925c657dd79b38c6a760024536c70fb8304
GIT binary patch
literal 658
zc$@)}0&V??P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00076Nkl<Zc-ocG
zUuaTM7y$6^{?RjUD}%vaODcnC3Yu1<k^QmJg5D!~?x}}SIdjrBHn;u|JqA6*8e6cS
zAc!qr)*zOvv6}snscBQJO{Z;a_bOiRy{Drx(Kr3zp2OkX?{~iQ9STT%egXZG1WrW(
zM3yo_Flo60SD}ueii2+qgcE1<93UM@--0=>Pg!2yuN!)QJyjR*X#(6d1%t8z5X~OI
zqu<=h&L*y!ivuRBzsU{Q=5wL=JJ%6vN4oc5ShCwMFs`TbxXW&VCQWngQ6!n6C`fN+
z@zl&xG7?{N{PDWSD?p#eBk`<`S^6!&<xUF#s{)LR=g}k=lVm7;a|=Py_a*#Dk(Gg!
zsXO@!aR1@3WV4-SdY=X0lKmKnz=9$ZfGS^xLkz@XtJpj1f0f?gyEzR?MBny6)fGHa
zN5?6q|9J>55`dQ&P?S<DfFTV+G={~mq0J8q!5d9TJ<ju4NX-t^0`2W5nZbz&be=WC
zzdTJUAV9eU&~gl<lk24lGNc0S9xk!``c^GqJ#J-2CRgCR%>?Nj1?7CI)C(0uL(482
z{NE$u{9^bqO+#P!jU!(T6ij9lJMuD)yUv+`RU-;l{L&)=Vrkgg&MMP0{!}!+)|2qM
z$7%qh(ZF^;31F34P3}!s_9`!l%TP8aDq=y(#HL-Xm7neoc?zrfR#v|^Biz2}Y=%0c
zrUHYpjs5u}AQAvI1d!x?q*=i>!%;*4NtWe<PDgL75uin*(TwU3AIe64MR)T&e~kc{
sMgTR(X{-n#tym}s0xx_dfLIUkA79!Q8NFY+nE(I)07*qoM6N<$g6xJUM*si-
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2e89f37b3111a2a77345cd4bbfbbd77a017713e4
GIT binary patch
literal 667
zc$@*70%ZM(P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0007FNkl<Zc-o~>
z%Zt)+5dZ!(TU}60^8uSA6e8k_AjE@+T|}W%2gP+4sR$O9Zr!9mU=JQ52qG3CH<d0y
zAPENDM8y>k<*K9}v^@C0_gkC!kqfp&3k?k4=X^dhpBc((wUYla>TduJheIxt$xc_R
z6{%LMcf#Rt<<bC5CX?ZIyI<ODwu9MhMq;tpt3V)dxL7QfmjbZc?YA@<%~y-XvW4ep
zG&;`b^Xf<>^8KFx5PV%E68V6AkI(1RcZEWMBoc||sZ{C+_LB<$`}_NkV9(-kID1?!
z_oi4Z-qC8c=byUWE=i};q~GtKLP=w=w+sej>i7GPDPX$d^?GMUqmfV)b)lT;bc%tr
zLm+*gOeUn=ZWEu+$3g%$2=>NgGVOxu8^I{xU@#!V;gF0*BLX54>`JrQB;|7X1ne;a
zTm`|j27@7}*XwV~<#MuIE@>8Y48(OlpVRlPR*O_Bl@HLB1K?;<AQ}aze6?Dwgfia2
zDi;ccG&`n?zGm)pI;2*sRg%f1Bb&{B1He8lA_&3)G%kQWfJ|OXBoaCZ9a%AW&7R3*
zo<KX_gI*nYU!AuRK(?Gt=Mj|il*i-I0qbBi+@l`3T<%3E6nX~d>Bbyj79Nl1lS-w!
z$7ZwX5>RP0Xf3|e<rIs>&(J;_0L(T3;M2Ct<@%yfDDGm*8jVI9QvQf<0)gN@=yDB#
zOQljW7z|o^z20yGz-%^4p$k8OX<V<@kI&A|PC%$CzUg$j8%m{eS1Of01ZFmfxD$`Z
zfBabj&b<Wha0I}SVf1?>IN;kjt>7q11AyoZegWbfT$8+@YV!a9002ovPDHLkV1oRD
BFXR9K
new file mode 100644
--- /dev/null
+++ b/mail/components/im/themes/imAccountWizard.css
@@ -0,0 +1,98 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2008.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#accountprotocol,
+#accountsummary {
+  overflow: visible;
+}
+
+#summarygrid {
+  overflow: auto;
+}
+
+groupbox.collapsable {
+  -moz-user-focus: normal;
+}
+
+%ifdef XP_WIN
+groupbox.collapsable .caption-text {
+  border: 1px solid transparent;
+}
+
+groupbox.collapsable:focus .caption-text {
+  border: 1px dotted ThreeDDarkShadow;
+}
+%endif
+
+groupbox.collapsable[closed="true"] {
+  border: none;
+  -moz-appearance: none;
+}
+
+groupbox[closed="true"] > .groupbox-body {
+  display: none;
+}
+
+%ifdef XP_MACOSX
+groupbox.collapsable caption .caption-icon {
+  width: 11px;
+  height: 11px;
+  background-repeat: no-repeat;
+  background-position: center;
+  -moz-margin-end: 2px;
+  background-image: url("chrome://global/skin/arrow/arrow-dn.gif");
+}
+
+groupbox.collapsable[closed="true"] caption .caption-icon {
+  background-image: url("chrome://global/skin/arrow/arrow-rit.gif");
+}
+%else
+groupbox.collapsable caption .caption-icon {
+  width: 9px;
+  height: 9px;
+  background-repeat: no-repeat;
+  background-position: center;
+  -moz-margin-start: 1px;
+  -moz-margin-end: 3px;
+  background-image: url("chrome://global/skin/tree/twisty-open.png");
+}
+
+
+groupbox.collapsable[closed="true"] caption .caption-icon {
+  background-image: url("chrome://global/skin/tree/twisty-clsd.png");
+}
+%endif
new file mode 100644
--- /dev/null
+++ b/mail/components/im/themes/imAccounts.css
@@ -0,0 +1,381 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2007.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Benedikt Pfeifer <benediktp@ymail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/* large parts copied from the addon manager */
+
+#accountManager {
+  background-color: ThreeDFace;
+  padding: 0 0;
+  margin: 0 0;
+}
+
+%ifndef XP_MACOSX
+notification > hbox {
+  border: none !important;
+  border-bottom: 1px solid ThreeDShadow !important;
+}
+%endif
+
+#accountsNotificationBox {
+  margin: 0 0;
+  -moz-appearance: none;
+}
+
+#noAccountScreen {
+  background-color: -moz-Dialog;
+  overflow: auto;
+}
+#noAccountBox {
+  background-color: -moz-Field;
+  color: -moz-FieldText;
+  border: 1px solid ThreeDShadow;
+  border-radius: 10px;
+  padding: 1.1em;
+  -moz-padding-start: 20px;
+  margin-left: 1em;
+  margin-right: 1em;
+}
+#noAccountImage {
+  list-style-image: url("chrome://global/skin/icons/information-64.png");
+  -moz-margin-end: 1.2em;
+}
+#noAccountInnerBox {
+  max-width: 25em;
+}
+#noAccountTitle {
+  margin: 0 1em 0.6em 0;
+  font-size: 160%;
+  border-bottom: 1px solid ThreeDLightShadow
+}
+#noAccountDesc {
+  font-size: 110%;
+}
+
+#accountlist {
+  margin: 0 0;
+  -moz-appearance: none;
+%ifdef WINCE
+  border: 2px solid;
+  -moz-border-top-colors: ThreeDShadow ThreeDDarkShadow;
+  -moz-border-right-colors: ThreeDHighlight ThreeDLightShadow;
+  -moz-border-bottom-colors: ThreeDHighlight ThreeDLightShadow;
+  -moz-border-left-colors: ThreeDShadow ThreeDDarkShadow;
+%else
+%ifndef XP_MACOSX
+  border-bottom: 2px solid;
+  -moz-border-bottom-colors: ThreeDHighlight ThreeDLightShadow;
+%endif
+}
+
+#bottombuttons {
+%ifdef XP_MACOSX
+  -moz-box-pack: end;
+  -moz-padding-start: 2px;
+  -moz-padding-end: 11px;
+  -moz-appearance: statusbar;
+  min-height: 28px;
+%else
+  margin: 0 0;
+%endif
+}
+
+%ifdef XP_MACOSX
+
+%filter substitution
+%define loweredShadow 0 1px rgba(255, 255, 255, .4)
+%define focusRingShadow 0 0 1px -moz-mac-focusring inset, 0 0 4px 1px -moz-mac-focusring, 0 0 1.5px 1px -moz-mac-focusring
+
+%define roundButtonShadow 0 1px rgba(255, 255, 255, .4)
+%define roundButtonPressedShadow inset 0 1px 3px rgba(0, 0, 0, .2), 0 1px rgba(255, 255, 255, .4)
+
+%define toolbarbuttonBorderColor rgba(59, 59, 59, 0.9)
+%define toolbarbuttonCornerRadius 3px
+%define toolbarbuttonBackground -moz-linear-gradient(top, #FFF, #ADADAD) repeat-x
+%define toolbarbuttonPressedInnerShadow inset rgba(0, 0, 0, 0.3) 0 -6px 10px, inset #000 0 1px 3px, inset rgba(0, 0, 0, 0.2) 0 1px 3px
+%define toolbarbuttonPressedBackgroundColor #B5B5B5
+%define toolbarbuttonInactiveBorderColor rgba(146, 146, 146, 0.84)
+%define toolbarbuttonInactiveFontColor #7C7C7C
+%define toolbarbuttonInactiveBackgroundImage -moz-linear-gradient(top, #FFF, #CCC)
+
+button {
+  -moz-appearance: none;
+  min-height: 18px;
+  min-width: 0;
+  margin: 0 3px;
+  padding: 0 2px;
+  text-shadow: @loweredShadow@;
+  border: 1px solid @toolbarbuttonBorderColor@;
+  border-radius: @toolbarbuttonCornerRadius@;
+  box-shadow: @loweredShadow@;
+  background: @toolbarbuttonBackground@;
+  background-origin: border-box;
+}
+
+button:focus {
+  box-shadow: @focusRingShadow@, @roundButtonShadow@;
+}
+
+button:active:hover:focus {
+  box-shadow: @focusRingShadow@, @roundButtonPressedShadow@;
+}
+ 
+button:hover:active:not([disabled="true"]) {
+  background: @toolbarbuttonPressedBackgroundColor@;
+  text-shadow: @loweredShadow@;
+  box-shadow: @toolbarbuttonPressedInnerShadow@, @loweredShadow@;
+}
+
+#bottombuttons button:-moz-window-inactive {
+  color: @toolbarbuttonInactiveFontColor@ !important; /* remove this when we support click-through, bug 392188 */
+  border-color: @toolbarbuttonInactiveBorderColor@;
+  background-image: @toolbarbuttonInactiveBackgroundImage@;
+  opacity: 0.7;
+}
+%else
+%ifndef XP_WIN
+#newaccount {
+  margin-left: 0;
+}
+
+#close {
+  margin-right: 0;
+}
+%else
+#newaccount,
+#close {
+  margin: 3px;
+}
+%endif
+%endif
+
+/* List Items */
+richlistitem[state="disconnected"] .accountIcon {
+  opacity: 0.3;
+}
+richlistitem[state="connecting"] .accountIcon,
+richlistitem[state="disconnected"][selected="true"] .accountIcon {
+  opacity: 0.7;
+}
+richlistitem[state="disconnected"]:not([selected="true"]) {
+  color: GrayText;
+}
+
+richlistitem[error="true"] .accountName {
+  color: rgb(150, 0, 0);
+}
+
+%ifndef XP_MACOSX
+/* When the error message was too long, the buttons were too small */
+richlistitem .account-buttons button {
+  min-height: 2em;
+}
+%endif
+
+richlistitem .account-buttons {
+  margin-top: 2px;
+%ifdef XP_MACOSX
+  margin-left: 35px;
+%else
+  margin-left: 32px;
+%endif
+}
+
+richlistitem[dragover="down"] {
+  border-bottom: 3px solid HighLight;
+}
+richlistitem[dragover="up"] {
+  border-top: 3px solid HighLight;
+}
+
+.error {
+  color: rgb(200, 0, 0);
+  margin-left: 6px;
+}
+.accountName {
+  font-weight: bold;
+}
+
+.accountIcon {
+  width: 32px;
+  max-width: 32px;
+  height: 32px;
+  max-height: 32px;
+}
+
+.accountStateIcon {
+  -moz-margin-start: 16px;
+  margin-top: 16px;
+  width: 16px;
+  height: 16px;
+}
+
+richlistitem[state="connected"] .accountStateIcon {
+  list-style-image: url("chrome://chat/skin/available-16.png");
+}
+richlistitem[state="disconnected"] .accountStateIcon {
+  list-style-image: url("chrome://chat/skin/offline-16.png");
+}
+richlistitem[state="connecting"] .accountStateIcon {
+  list-style-image: url("chrome://global/skin/icons/loading_16.png");
+}
+richlistitem[error="true"] .accountStateIcon {
+  list-style-image: url("chrome://global/skin/icons/warning-16.png");
+}
+
+
+#statusArea {
+  margin: 0 0;
+  padding: 0 0;
+}
+
+#displayName {
+  font-size: large;
+  border-bottom: 1px solid rgba(0,0,0,0.25);
+  margin: 0 0 16px;
+}
+
+#displayName[usingDefault]:not([editing]) {
+  color: GrayText;
+}
+
+#userIcon {
+  border: 2px solid rgba(0,0,0,0.15);
+  border-radius: 5px;
+  max-width: 48px;
+  max-height: 48px;
+  width: 48px;
+  height: 48px;
+}
+
+#userIcon[src=""] {
+  background-image: url("chrome://messenger/skin/userIcon.png");
+  background-size: contain;
+  background-repeat: no-repeat;
+}
+
+#userIcon:hover {
+  border-color: rgba(0,0,0,0.35);
+  background-color: rgba(0,0,0,0.35);
+  opacity: .4;
+}
+
+#statusTypeIcon {
+  margin: 32px 0 0 32px;
+  width: 16px;
+  height: 16px;
+  min-height: 16px;
+  min-width: 16px;
+  -moz-appearance: none;
+  background: transparent;
+  box-shadow: none;
+  border: none;
+}
+
+#statusImageStack,
+#displayNameAndstatusMessageStack {
+  margin: 3px 2px;
+}
+
+#statusTypeIcon dropmarker {
+  display: none;
+}
+
+%ifdef XP_WIN
+#statusTypeIcon .button-box {
+  padding: 0 0;
+  border: none;
+}
+%endif
+
+#statusTypeIcon[status="available"],
+#statusTypeAvailable {
+  list-style-image: url('chrome://chat/skin/available-16.png');
+}
+
+#statusTypeIcon[status="unavailable"],
+#statusTypeIcon[status="away"],
+#statusTypeUnavailable {
+  list-style-image: url('chrome://chat/skin/away-16.png');
+}
+
+#statusTypeIcon[status="offline"],
+#statusTypeIcon[status="invisible"],
+#statusTypeOffline {
+  list-style-image: url('chrome://chat/skin/offline-16.png');
+}
+
+#statusTypeIcon[status="idle"] {
+  list-style-image: url('chrome://chat/skin/idle-16.png');
+}
+
+%ifdef XP_MACOSX
+#statusMessage {
+  margin: 32px 0 0;
+  min-height: 16px;
+}
+
+#statusMessage[editing] {
+  margin: 29px 2px 0 -4px;
+}
+
+#displayName[editing] {
+  margin-left: -4px;
+}
+%else
+#statusMessage {
+  margin: 32px 0 0;
+}
+
+#statusMessage[editing] {
+%ifdef XP_WIN
+  margin: 30px 0 1px -2px;
+%else
+  margin: 30px -2px -2px -4px;
+%endif
+}
+
+#displayName[editing] {
+%ifdef XP_WIN
+  margin-left: -2px;
+%else
+  margin-left: -4px;
+  margin-right: -2px;
+%endif
+  margin-bottom: 18px;
+}
+%endif
new file mode 100644
--- /dev/null
+++ b/mail/components/im/themes/imBuddytooltip.css
@@ -0,0 +1,53 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2007.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+@import url("chrome://messenger/skin/imStatus.css");
+
+.protoIcon[status="unknown"] {
+  opacity: 0.7;
+}
+
+.tooltip-header {
+  font-weight: bold;
+  font-size: 14pt;
+  -moz-margin-end: 3px;
+  margin-bottom: 0 !important;
+}
+
+.prplTooltipBuddyIcon {
+  margin: 0 6px;
+}
new file mode 100644
--- /dev/null
+++ b/mail/components/im/themes/imMenulist.css
@@ -0,0 +1,46 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2007.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+
+/* Fix the icons in the menulist by undoing things from menu.css */
+menulist > menupopup > menuitem.menuitem-iconic > .menu-iconic-left {
+  display: -moz-box !important;
+}
+
+menulist > menupopup > menuitem.menuitem-iconic > .menu-iconic-text {
+  margin-left: 2px !important;
+}
new file mode 100644
--- /dev/null
+++ b/mail/components/im/themes/imRichlistbox.css
@@ -0,0 +1,83 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2009.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/* Mostly copied from the download manager */
+
+/* List Items */
+richlistitem {
+  padding-top: 6px;
+  padding-bottom: 6px;
+  -moz-padding-start: 4px;
+  -moz-padding-end: 4px;
+  min-height: 25px;
+%ifndef XP_MACOSX
+%ifdef XP_WIN
+  border-bottom: 1px solid ThreeDLightShadow;
+%else
+  border-bottom: 1px dotted #C0C0C0;
+%endif
+%endif
+}
+
+%ifdef XP_MACOSX
+richlistitem:not([selected="true"]):nth-child(odd) {
+  background-color: -moz-oddtreerow;
+}
+%endif
+
+%ifdef XP_WIN
+richlistitem[selected="true"] {
+  background-image: url(chrome://mozapps/skin/extensions/itemEnabledFader.png);
+}
+
+richlistitem[selected="true"]:not(:-moz-window-inactive) {
+  background-color: Highlight;
+  color: HighlightText;
+}
+%else
+%ifdef XP_MACOSX
+richlistitem[selected="true"]:not(:-moz-window-inactive) {
+  background-color: Highlight;
+  color: HighlightText;
+}
+%else
+richlistitem[selected="true"] {
+  background-color: -moz-Dialog;
+  color: -moz-DialogText;
+}
+%endif
+%endif
new file mode 100644
--- /dev/null
+++ b/mail/components/im/themes/imStatus.css
@@ -0,0 +1,68 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2007.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+.statusIcon[status="away"],
+.statusIcon[status="unavailable"] {
+  background-image: url('chrome://chat/skin/away.png');
+}
+
+.statusIcon[status="idle"] {
+  background-image: url('chrome://chat/skin/idle.png');
+}
+
+.statusIcon[status="mobile"] {
+  background-image: url('chrome://chat/skin/mobile.png');
+}
+
+.statusIcon[status="offline"],
+.statusIcon[status="left"] {
+  background-image: url('chrome://chat/skin/offline.png');
+}
+
+.statusIcon[status="unknown"] {
+  background-image: url('chrome://chat/skin/unknown.png');
+}
+
+.protoIcon[status="offline"],
+.protoIcon[status="unknown"] {
+  opacity: 0.3;
+}
+
+.protoIcon[status="offline"][selected],
+.protoIcon[status="unknown"][selected] {
+  opacity: 0.7;
+}
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1853d3a0192bd8a6f0125dfd084f67c2862b5452
GIT binary patch
literal 727
zc$@*(0x127P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0007>Nkl<Zc-p0s
zT}V_x6vzMSTwR51CCSy*GBV4MO2s1jAhn2S4aFivx2&Wx${>(}(4-zJgMCO#2~$X|
zhoY1f)JiS%p%1hk`XEdbHCIL3U3J%8@7%qe+b)qsR5WmAhB<S7=YQsm7yfg2{myZ}
z0Fmnx(Cw0dXI+TTFrfb*0D^aGu~?Rzlb#33hPIlA<tfF&aX@ta6`(W^OS4k&Hb{5N
z4}1`V4QCw<orpbAj@~~4%Jc|cxdeu!ed&6HXVOU@(0db2wLL}GD^SvChVvJ|<02fA
zWe5v`p$`qgVogM_7IBx!I}7k3!0`d*=l5V5Lifxx9A+zOm=9)?icv)XA11a)B3qje
zsA2$bH6cYZ3oTy=@EMRs0H=xebmp)Db}L5S+Yy@SBM^Y89-ZTrTeH_?VNo_9)3duA
z6f{XTK{iI9aW_mNMUn(=>O)%#>b75CxeCBnznQN(sl%OwwAd(Uw@@ZdkSV%srM;Pg
zX_csSNrW`okM?HNZ#&QONah3q6cw^Mdjnr_YBz4j?TU_sI+`xq2{KK=w(pr?8NjP{
z-d}3O-s-399nq&fU0?!D^D1(A#*t&bH=tcjJ_>R<-GPdcD$z86s#+XATFLIx3zIBS
z{-KS`&v=HH==KNZBQS!3s0^8V0C1OO9YbUN?A6putiYXN&Ocy6$3@<}agQb$zSIeB
zr^g>471XDoh05;)W~R{DG}*eTknNxg;v7I|7y=qk<4L?OED@Gb^mI$+kDp*vd{L1|
zT^8dN1poQy>ae^>y@DjuFzj;xg=x^_?1V{B!SVVHsvf*VU3D89QhW!Jd3bsvveqoe
zLD31-wFYPn%^3c<1V5TWJZFkLCBuxz=}#8mt^+dJ__rEqd<9mM6+ACs>I(n>002ov
JPDHLkV1lx*M!En1
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..43cc90eea0bff35daf698119be6da11c61a546f6
GIT binary patch
literal 733
zc$@*<0wVp1P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0007{Nkl<Zc-n<i
zOGs2<6#o7@cOJg3I--J*q~I>7Xwjlw1ZA&HBF!{vx~VY2aWn}cDxx5UnIwr?n2H&u
z)uyC^TD5DD3)@79;=<@LK5pYU&fK|w-|s{kOm!~@&iT(d-~avRA%Hdj7L2YTz{XZH
z(liU`$cl!I&;AuRP(yXbjQD8_{%EF>fzkQk2qb3BXkcmsz_AG+lLZJ3ZYMys8TCE;
z4Pb<Z4l=ox5q*Ukyz}9fg<K4(&i9waGl((vEM?k^o_HaQE~$KDjOZkqp=(%+g#**M
z7?gDn)>$^3(uT={2&GXNL3x0R-;u(Zv<p|_Ui1{kvBOH?Lo(XBrc(1l18#>4+u{Y7
zDq6q|J&8*h7w*wDEQ}7RZojWd)RWd*;lzw3;V(@hG<T<I{q8O8!&vkn>=YQ4X+BkA
zkFDVqT_+X5dn(6-#5h?uvQB-rj?2=8Vvocl_4ri*pGaXzvv5`DXh$03Qp0ij#uUI3
zl?qBSWx=64kton$NFAR~?L(PjaQ6+qYiU%h*hpGwxScvV0EMjCyaG^$kYYWtD3L-e
z&xP__0Q4>AyHJv_u|mdqbV&v9k;-1m1iNvVjU5pggfc}a-TVYc95K?q#Y8`2CMZYA
z<Fx`9RhLj2*!GvKgK68S_a<=Rk0R#dx+YjS&JsJip8!t@a9Xu7$e5C39&USn;G&}%
z9o9T{>Nchqds|h%;SCNHr!~~-RhZ1op*NY2Yo1k{qbnwiPS#uEISn6?cROA9_^&}q
zr%=b+-|^rf^A53)0gm!;0}VsXL$VS^s;9-wHOlUr{l)IDSf<*>_+^grlk6^ZuWw=_
z4?mfa=E-d0UzVwBa2I21XgoF}fsJj1q7WP@H=|A8{<F@NX4v;7YXd(4ixV8e1YZ*U
P00000NkvXXu0mjf37b>O
new file mode 100644
--- /dev/null
+++ b/mail/components/preferences/chat.js
@@ -0,0 +1,63 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Instantbird messenging client, released
+ * 2010.
+ *
+ * The Initial Developer of the Original Code is
+ * Benedikt P. <leeraccount@yahoo.de>.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+var gChatPane = {
+  init: function ()
+  {
+    this.updateDisabledState();
+  },
+
+  updateDisabledState: function ()
+  {
+    let broadcaster = document.getElementById("idleReportingEnabled");
+    if (document.getElementById("messenger.status.reportIdle").value) {
+      broadcaster.removeAttribute("disabled");
+      this.updateMessageDisabledState();
+    }
+    else
+      broadcaster.setAttribute("disabled", "true");
+  },
+
+  updateMessageDisabledState: function ()
+  {
+    let textbox = document.getElementById("defaultIdleAwayMessage");
+    if (document.getElementById("messenger.status.awayWhenIdle").value)
+      textbox.removeAttribute("disabled");
+    else
+      textbox.setAttribute("disabled", "true");
+  }
+};
new file mode 100644
--- /dev/null
+++ b/mail/components/preferences/chat.xul
@@ -0,0 +1,109 @@
+<?xml version="1.0"?>
+# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is the Instantbird Preferences Window.
+#
+# The Initial Developer of the Original Code is
+#   Florian Queze <florian@instantbird.org>.
+# Portions created by the Initial Developer are Copyright (C) 2009
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+<!DOCTYPE overlay [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+<!ENTITY % chatDTD SYSTEM "chrome://messenger/locale/preferences/chat.dtd">
+%brandDTD;
+%chatDTD;
+]>
+
+<overlay id="ChatPaneOverlay"
+         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+  <prefpane id="paneChat" onpaneload="gChatPane.init();">
+    <script type="application/javascript" src="chrome://messenger/content/preferences/chat.js"/>
+
+    <preferences id="chatPreferences">
+      <preference id="messenger.startup.action"            name="messenger.startup.action"     type="int"/>
+      <preference id="purple.conversations.im.send_typing" name="purple.conversations.im.send_typing" type="bool"/>
+      <preference id="messenger.status.reportIdle"         name="messenger.status.reportIdle"         type="bool"
+                  onchange="gChatPane.updateDisabledState();"/>
+      <preference id="messenger.status.timeBeforeIdle"     name="messenger.status.timeBeforeIdle"     type="int"/>
+      <preference id="messenger.status.awayWhenIdle"       name="messenger.status.awayWhenIdle"       type="bool"
+                  onchange="gChatPane.updateMessageDisabledState();"/>
+      <preference id="messenger.status.defaultIdleAwayMessage" name="messenger.status.defaultIdleAwayMessage" type="wstring"/>
+      <preference id="purple.logging.log_chats"            name="purple.logging.log_chats"            type="bool"/>
+      <preference id="purple.logging.log_ims"              name="purple.logging.log_ims"              type="bool"/>
+      <preference id="purple.logging.log_system"           name="purple.logging.log_system"           type="bool"/>
+      <preference id="pref.privacy.disable_button.view_passwords" name="pref.privacy.disable_button.view_passwords" type="bool"/>
+    </preferences>
+
+    <broadcaster id="idleReportingEnabled"/>
+
+    <!-- Startup -->
+    <hbox align="center">
+      <label value="&startupAction.label;" accesskey="&startupAction.accesskey;"
+             control="messengerStartupAction"/>
+      <menulist id="messengerStartupAction" preference="messenger.startup.action">
+        <menupopup>
+          <menuitem label="&startupOffline.label;"     value="0"/>
+          <menuitem label="&startupConnectAuto.label;" value="1"/>
+        </menupopup>
+      </menulist>
+    </hbox>
+    <separator/>
+
+    <!-- Status -->
+    <hbox align="center">
+      <checkbox id="reportIdle" label="&reportIdleAfter.label;"
+                accesskey="&reportIdleAfter.accesskey;"
+                preference="messenger.status.reportIdle"/>
+      <textbox id="timeBeforeAway" type="number" min="1"
+               observes="idleReportingEnabled"
+               preference="messenger.status.timeBeforeIdle"
+               onsyncfrompreference="var elt = document.getElementById(this.getAttribute('preference'));
+                                     return (elt.instantApply ? elt.valueFromPreferences : elt.value) / 60;"
+               onsynctopreference="return this.value * 60;"/>
+      <label value="&idleTime;" control="timeBeforeAway"/>
+    </hbox>
+    <vbox class="indent">
+      <checkbox id="autoAway" label="&andSetStatusToAway.label;"
+                observes="idleReportingEnabled"
+                accesskey="&andSetStatusToAway.accesskey;"
+                preference="messenger.status.awayWhenIdle"/>
+      <textbox id="defaultIdleAwayMessage" class="indent"
+               observes="idleReportingEnabled"
+               preference="messenger.status.defaultIdleAwayMessage"/>
+    </vbox>
+    <separator/>
+    <checkbox id="sendTyping" label="&sendTyping.label;"
+              accesskey="&sendTyping.accesskey;"
+              preference="purple.conversations.im.send_typing"/>
+  </prefpane>
+</overlay>
--- a/mail/components/preferences/jar.mn
+++ b/mail/components/preferences/jar.mn
@@ -1,14 +1,15 @@
 messenger.jar:
 *   content/messenger/preferences/preferences.xul
 *   content/messenger/preferences/general.xul
 *   content/messenger/preferences/general.js
 *   content/messenger/preferences/display.xul
     content/messenger/preferences/display.js
+*   content/messenger/preferences/chat.xul
 *   content/messenger/preferences/compose.xul
 *   content/messenger/preferences/compose.js
 *   content/messenger/preferences/sendoptions.xul
 *   content/messenger/preferences/sendoptions.js
 *   content/messenger/preferences/security.xul
 *   content/messenger/preferences/security.js
 *   content/messenger/preferences/junkLog.xul
 *   content/messenger/preferences/junkLog.js
--- a/mail/components/preferences/preferences.xul
+++ b/mail/components/preferences/preferences.xul
@@ -96,15 +96,17 @@
   <script type="application/javascript" src="chrome://communicator/content/contentAreaClick.js"/>
 
   <prefpane id="paneGeneral" label="&paneGeneral.title;"
             src="chrome://messenger/content/preferences/general.xul"/>
   <prefpane id="paneDisplay" label="&paneDisplay.title;"
             src="chrome://messenger/content/preferences/display.xul"/>
   <prefpane id="paneCompose" label="&paneComposition.title;"
             src="chrome://messenger/content/preferences/compose.xul"/>
+  <prefpane id="paneChat" label="&paneChat.title;"
+            src="chrome://messenger/content/preferences/chat.xul"/>
   <prefpane id="paneSecurity" label="&paneSecurity.title;"
             src="chrome://messenger/content/preferences/security.xul"/>
   <prefpane id="paneApplications" label="&paneAttachments.title;"
             src="chrome://messenger/content/preferences/applications.xul"/>
   <prefpane id="paneAdvanced" label="&paneAdvanced.title;"
             src="chrome://messenger/content/preferences/advanced.xul"/>
 </prefwindow>
--- a/mail/installer/package-manifest.in
+++ b/mail/installer/package-manifest.in
@@ -239,16 +239,55 @@
 
 @BINPATH@/components/mdn-service.js
 @BINPATH@/components/mdn-service.manifest
 @BINPATH@/components/smime-service.js
 @BINPATH@/components/smime-service.manifest
 @BINPATH@/components/msgsmime.xpt
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; instant messaging
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; shared with Instantbird
+@BINPATH@/@PREF_DIR@/chat-prefs.js
+@BINPATH@/chrome/chat@JAREXT@
+@BINPATH@/chrome/chat.manifest
+@BINPATH@/components/chat.xpt
+@BINPATH@/components/imAccounts.js
+@BINPATH@/components/imAccounts.manifest
+@BINPATH@/components/imCommands.js
+@BINPATH@/components/imCommands.manifest
+@BINPATH@/components/imContacts.js
+@BINPATH@/components/imContacts.manifest
+@BINPATH@/components/imConversations.js
+@BINPATH@/components/imConversations.manifest
+@BINPATH@/components/imCore.js
+@BINPATH@/components/imCore.manifest
+@BINPATH@/components/facebook.js
+@BINPATH@/components/facebook.manifest
+@BINPATH@/components/gtalk.js
+@BINPATH@/components/gtalk.manifest
+@BINPATH@/components/twitter.js
+@BINPATH@/components/twitter.manifest
+@BINPATH@/components/irc.js
+@BINPATH@/components/irc.manifest
+@BINPATH@/components/xmpp.js
+@BINPATH@/components/xmpp.manifest
+@BINPATH@/components/smileProtocolHandler.js
+@BINPATH@/components/smileProtocolHandler.manifest
+@BINPATH@/components/logger.js
+@BINPATH@/components/logger.manifest
+
+; Thunderbird specific
+@BINPATH@/@PREF_DIR@/all-im.js
+@BINPATH@/components/im.manifest
+@BINPATH@/components/imIncomingServer.js
+@BINPATH@/components/imProtocolInfo.js
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ; Chrome Files
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 @BINPATH@/chrome/classic@JAREXT@
 @BINPATH@/chrome/classic.manifest
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ; Default Profile Settings
--- a/mail/locales/Makefile.in
+++ b/mail/locales/Makefile.in
@@ -105,16 +105,17 @@ libs:: $(addsuffix .xml,$(SEARCH_PLUGINS
 	$(SYSINSTALL) $(IFLAGS1) $^ $(FINAL_TARGET)/searchplugins
 
 install:: $(addsuffix .xml,$(SEARCH_PLUGINS))
 	$(SYSINSTALL) $(IFLAGS1) $^ $(DESTDIR)$(mozappdir)/searchplugins
 
 libs-%:
 	$(NSINSTALL) -D $(DIST)/install
 	@$(MAKE) -C ../../mozilla/toolkit/locales libs-$* BOTH_MANIFESTS=1
+	@$(MAKE) -C ../../chat AB_CD=$* XPI_NAME=locale-$* BOTH_MANIFESTS=1
 	@$(MAKE) -C ../../editor/ui/locales AB_CD=$* XPI_NAME=locale-$* BOTH_MANIFESTS=1
 	@$(MAKE) -C ../../mozilla/extensions/spellcheck/locales AB_CD=$* XPI_NAME=locale-$* BOTH_MANIFESTS=1
 	@$(MAKE) -C ../../mozilla/intl/locales AB_CD=$* XPI_NAME=locale-$* BOTH_MANIFESTS=1
 	@$(MAKE) libs AB_CD=$* XPI_NAME=locale-$* PREF_DIR=defaults/pref BOTH_MANIFESTS=1
 	@$(MAKE) -C $(DEPTH)/$(MOZ_BRANDING_DIRECTORY)/locales AB_CD=$* XPI_NAME=locale-$* BOTH_MANIFESTS=1
 
 
 # Note the funny extra "../" in SFX_HEADER is because the repackage-zip command
--- a/mail/locales/en-US/chrome/messenger/AccountManager.dtd
+++ b/mail/locales/en-US/chrome/messenger/AccountManager.dtd
@@ -1,16 +1,18 @@
 <!-- extracted from AccountManager.xul -->
 
 <!ENTITY accountManagerTitle.label "Account Settings">
 
 <!ENTITY accountActionsButton.label "Account Actions">
 <!ENTITY accountActionsButton.accesskey "A">
 <!ENTITY addMailAccountButton.label "Add Mail Account…">
 <!ENTITY addMailAccountButton.accesskey "A">
+<!ENTITY addIMAccountButton.label "Add Chat Account…">
+<!ENTITY addIMAccountButton.accesskey "C">
 <!ENTITY addOtherAccountButton.label "Add Other Account…">
 <!ENTITY addOtherAccountButton.accesskey "O">
 <!ENTITY setDefaultButton.label "Set as Default">
 <!ENTITY setDefaultButton.accesskey "D">
 <!ENTITY removeButton.label "Remove Account">
 <!ENTITY removeButton.accesskey "R">
 
 <!-- AccountManager.xul -->
new file mode 100644
--- /dev/null
+++ b/mail/locales/en-US/chrome/messenger/addbuddy.dtd
@@ -0,0 +1,3 @@
+<!ENTITY addBuddyWindow.title           "Add contact">
+<!ENTITY name.label                     "Username">
+<!ENTITY account.label                  "Account">
new file mode 100644
--- /dev/null
+++ b/mail/locales/en-US/chrome/messenger/am-im.dtd
@@ -0,0 +1,12 @@
+<!ENTITY accountWindow.title           "Account properties">
+<!ENTITY accountWindow.width           "300">
+<!ENTITY account.general               "General">
+<!ENTITY account.advanced              "Advanced Options">
+<!ENTITY account.name                  "Username:">
+<!ENTITY account.password              "Password:">
+<!ENTITY account.alias                 "Alias:">
+<!ENTITY account.newMailNotification   "Notify on new Mail">
+<!ENTITY account.autojoin              "Auto-Joined Channels:">
+<!ENTITY account.proxySettings.caption "Proxy Settings:">
+<!ENTITY account.proxySettings.change.label     "Change…">
+<!ENTITY account.proxySettings.change.accessKey "C">
new file mode 100644
--- /dev/null
+++ b/mail/locales/en-US/chrome/messenger/chat.dtd
@@ -0,0 +1,51 @@
+<!ENTITY onlineContactsHeader.label    "Online Contacts">
+<!ENTITY offlineContactsHeader.label   "Offline Contacts">
+<!ENTITY conversationsHeader.label     "Conversations">
+<!ENTITY searchResultConversation.label "Search result">
+<!ENTITY chat.noConv.title             "Conversations will be displayed here.">
+<!ENTITY chat.noConv.description       "Use the contact list in the left panel to start a conversation.">
+<!ENTITY chat.noPreviousConv.description       "&brandShortName; currently doesn't have any previous conversations stored for this contact.">
+<!ENTITY chat.noAccount.title             "You haven't set up a chat account yet.">
+<!ENTITY chat.noAccount.description       "Let &brandShortName; guide you through the process of setting up your chat account.">
+<!ENTITY chat.accountWizard.button       "Get started">
+<!ENTITY chat.noConnectedAccount.title             "Your chat accounts are not connected.">
+<!ENTITY chat.noConnectedAccount.description       "You can connect them from the 'Chat status' dialog:">
+<!ENTITY chat.showAccountManager.button       "Show chat status">
+
+<!ENTITY chat.participants             "Participants:">
+<!ENTITY chat.previousConversations    "Previous Conversations:">
+<!ENTITY chat.ongoingConversation      "Ongoing conversation">
+
+<!ENTITY openConversationCmd.label     "Start a Conversation">
+<!ENTITY openConversationCmd.accesskey "c">
+<!ENTITY closeConversationCmd.label    "Close Conversation">
+<!ENTITY closeConversationCmd.accesskey "C">
+<!ENTITY aliasCmd.label                "Rename">
+<!ENTITY aliasCmd.accesskey            "R">
+<!ENTITY deleteCmd.label               "Remove Contact">
+<!ENTITY deleteCmd.accesskey           "v">
+
+<!ENTITY openConversationButton.tooltip  "Start a conversation">
+<!ENTITY closeConversationButton.tooltip "Close conversation">
+
+<!ENTITY addBuddyButton.label          "Add Contact">
+<!ENTITY joinChatButton.label          "Join Chat">
+<!ENTITY chatAccountsButton.label      "Show Accounts">
+
+<!-- LOCALIZATION NOTE (searchAllChatMessages.label.base):
+     This is the base of the empty text for the chat search box.  We replace
+     #1 with the contents of the appropriate search.keyLabel.* value for the
+     platform (defined in messenger/messenger.dtd).
+     The goal is to convey to the user that typing in the box will allow them
+     to search for conversations and that there is a hotkey they can press
+     to get to the box faster.  If the global indexer is disabled, the search
+     box will be collapsed and the user will never see this message.
+     -->
+<!ENTITY searchAllChatMessages.label.base "Search all conversations… #1">
+
+<!ENTITY status.available          "Available">
+<!ENTITY status.unavailable        "Unavailable">
+<!ENTITY status.offline            "Offline">
+
+<!ENTITY openLinkCmd.label            "Open Link…">
+<!ENTITY openLinkCmd.accesskey        "O">
new file mode 100644
--- /dev/null
+++ b/mail/locales/en-US/chrome/messenger/chat.properties
@@ -0,0 +1,86 @@
+chatTabTitle=Chat
+goBackToCurrentConversation.button=Back to current conversation
+# LOCALIZATION NOTE (startAConversationWith.button):
+#  %S is replaced with the display name of a contact.
+startAConversationWith.button=Start a conversation with %S
+
+# LOCALIZATION NOTE (defaultGroup):
+#  this is used in the addBuddies dialog if the list of existing groups is empty
+defaultGroup=Contacts
+
+# LOCALIZATION NOTE (buddy.authRequest.label):
+# This string appears in a notification bar at the
+# top of the Contacts window when someone added the user to his/her
+# contact list, to request the permission from the user to share
+# status information with this potential new contact.
+# %S is replaced with the user name of the potential new contact.
+buddy.authRequest.label=%S wants to chat with you
+buddy.authRequest.allow.label=Allow
+buddy.authRequest.allow.accesskey=A
+buddy.authRequest.deny.label=Deny
+buddy.authRequest.deny.accesskey=D
+
+# LOCALIZATION NOTE (buddy.deletePrompt.title):
+# %S here will be replaced by the alias (or username) of a buddy about
+# to be removed from the buddy list.
+buddy.deletePrompt.title=Delete %S?
+
+# LOCALIZATION NOTE (buddy.deletePrompt.message):
+# %1$S will be replaced by the name of a buddy (either the alias
+# followed by the username between parenthesis if an alias is set, or
+# only the username otherwise).
+# %2$S will be the name of the protocol on which this buddy is removed
+# (for example: AIM, MSN, Google Talk).
+#
+# Please find a wording that will keep the username as close as
+# possible to the beginning of the string, because this is the
+# important information that an user should see when looking quickly
+# at this prompt.
+buddy.deletePrompt.message=%1$S will be permanently removed from your %2$S buddy list if you continue.
+
+# LOCALIZATION NOTE (buddy.deletePrompt.displayName):
+# This is used to format the display name inserted in buddy.deletePrompt.message
+# %1$S is the alias, %2$S is the username.
+buddy.deletePrompt.displayName=%1$S (%2$S)
+
+# LOCALIZATION NOTE (buddy.deletePrompt.button):
+# the & symbol indicates the position of the character that should be
+# used as the accesskey for this button.
+buddy.deletePrompt.button=&Delete
+
+displayNameEmptyText=Display Name
+userIconFilePickerTitle=Select the new icon…
+
+# LOCALIZATION NOTE (chat.isTyping, chat.hasStoppedTyping):
+# The contact display name is displayed with a big font on a first
+# line and these two strings are displayed on a second line with a
+# smaller font. Please try to find a wording that make this look
+# almost like a sentence.
+chat.isTyping=is typing…
+chat.hasStoppedTyping=has stopped typing.
+# LOCALIZATION NOTE (chat.contactIsTyping, chat.contactHasStoppedTyping):
+#  These strings are displayed in a tooltip when hovering the status type icon.
+#  %S is replaced with the display name of the contact.
+chat.contactIsTyping=%S is typing.
+chat.contactHasStoppedTyping=%S has stopped typing.
+
+noTopic=No topic message for this room.
+
+# LOCALIZATION NOTE (unknownCommand):
+# This is shown when an unknown command (/foo) is attempted. %S is the command.
+unknownCommand=%S is not a supported command. Type /help to see the list of commands.
+
+# LOCALIZATION NOTE (buddytooltip.username, buddytooltip.account):
+# This is for the tooltip that appears when hovering
+# a contact or a conversation in the left pane.
+buddytooltip.username=Username
+buddytooltip.account=Account
+
+# LOCALIZATION NOTE (today, yesterday):
+# These 3 strings are used to display when previous conversations happened.
+# In 'today' and 'yesterday', %S is replaced with the time.
+today=Today %S
+yesterday=Yesterday %S
+# LOCALIZATION NOTE (dateTime):
+# %1$S is the date, %2$S is the date.
+dateTime=%1$S %2$S
new file mode 100644
--- /dev/null
+++ b/mail/locales/en-US/chrome/messenger/imAccountWizard.dtd
@@ -0,0 +1,28 @@
+<!ENTITY windowTitle.label            "Chat Account Wizard">
+
+<!ENTITY accountProtocolTitle.label   "Chat network">
+<!ENTITY accountProtocolInfo.label    "Please choose the network of your chat account.">
+<!ENTITY accountProtocolField.label   "Network:">
+<!ENTITY accountProtocolGetMore.label "Get more…">
+
+<!ENTITY accountUsernameTitle.label   "Username">
+<!ENTITY accountUsernameDuplicate.label "This account is already configured!">
+
+<!ENTITY accountPasswordTitle.label   "Password">
+<!ENTITY accountPasswordInfo.label    "Please enter your password in the box below.">
+<!ENTITY accountPasswordField.label   "Password:">
+<!ENTITY accountPasswordManager.label "The password entered here will be stored in the Password Manager. Leave this box empty if you want to be prompted for your password each time this account is connected.">
+
+<!ENTITY accountAdvancedTitle.label   "Advanced Options">
+<!ENTITY accountAdvancedInfo.label    "Feel free to skip this step if you want to.">
+<!ENTITY accountAdvanced.newMailNotification.label   "Notify on new Mail">
+<!ENTITY accountAliasGroupbox.caption "Local Alias">
+<!ENTITY accountAliasField.label      "Alias:">
+<!ENTITY accountAliasInfo.label       "This will only be displayed in your conversations when you talk, remote contacts won't see it.">
+<!ENTITY accountProxySettings.caption "Proxy Settings">
+<!ENTITY accountProxySettings.change.label     "Change…">
+<!ENTITY accountProxySettings.change.accessKey "C">
+
+<!ENTITY accountSummaryTitle.label   "Summary">
+<!ENTITY accountSummaryInfo.label    "A summary of the information you entered is displayed below. Please check it before the account is created.">
+<!ENTITY accountSummary.connectNow.label "Connect this account now.">
new file mode 100644
--- /dev/null
+++ b/mail/locales/en-US/chrome/messenger/imAccounts.dtd
@@ -0,0 +1,26 @@
+<!ENTITY accountsWindow.title                "Instant messaging status">
+<!ENTITY accountsWindow.style                "width: 28em; height: 19em;">
+<!ENTITY accountManager.newAccount.label     "New Account">
+<!ENTITY accountManager.newAccount.accesskey "N">
+<!ENTITY accountManager.close.label          "Close">
+<!ENTITY accountManager.close.accesskey      "l">
+<!-- This should match account.commandkey in instantbird.dtd -->
+<!ENTITY accountManager.close.commandkey     "a">
+<!-- This title must be short, displayed with a big font size -->
+<!ENTITY accountManager.noAccount.title      "No account configured yet">
+<!ENTITY accountManager.noAccount.description "Click on the &accountManager.newAccount.label; button to let &brandShortName; guide you through the process of configuring one.">
+<!ENTITY account.autoSignOn.label     "Sign-on at startup">
+<!ENTITY account.autoSignOn.accesskey "S">
+<!ENTITY account.connect.label        "Connect">
+<!ENTITY account.connect.accesskey    "o">
+<!ENTITY account.disconnect.label     "Disconnect">
+<!ENTITY account.disconnect.accesskey "i">
+<!ENTITY account.edit.label           "Properties">
+<!ENTITY account.edit.accesskey       "P">
+<!ENTITY account.moveup.label         "Move up">
+<!ENTITY account.movedown.label       "Move down">
+<!ENTITY account.cancelReconnection.label         "Cancel reconnection">
+<!ENTITY account.cancelReconnection.accesskey     "a">
+<!ENTITY account.connecting           "Connecting…">
+<!ENTITY account.disconnecting        "Disconnecting…">
+<!ENTITY account.disconnected         "Not Connected">
new file mode 100644
--- /dev/null
+++ b/mail/locales/en-US/chrome/messenger/imAccounts.properties
@@ -0,0 +1,59 @@
+# LOCALIZATION NOTE (protoOptions):
+# %S is replaced by the name of a protocol
+protoOptions=%S Options
+accountUsername=Username:
+# LOCALIZATION NOTE (accountColon):
+# This string is used to append a colon after the label of each
+# option. It's localizable so that the typography can be adapted.
+accountColon=%S:
+# LOCALIZATION NOTE (accountUsernameInfo):
+# %S is replaced by the name of a protocol
+accountUsernameInfo=Please enter the username for your %S account.
+# LOCALIZATION NOTE (accountUsernameInfoWithDescription):
+# %1$S is a hint for the expected format of the username
+# %2$S is the name of a protocol
+accountUsernameInfoWithDescription=Please enter the username (%1$S) for your %2$S account.
+
+# LOCALIZATION NOTE (account.connection.error):
+# %S is the error message.
+account.connection.error=Error: %S
+# LOCALIZATION NOTE (account.connection.errorUnknownPrpl)
+# %S is the id (not very user friendly; hence the quotes) of the missing plugin.
+account.connection.errorUnknownPrpl=No '%S' protocol plugin.
+account.connection.errorEnteringPasswordRequired=Entering a password is required to connect this account.
+account.connection.errorCrashedAccount=A crash occurred while connecting this account.
+# LOCALIZATION NOTE (account.connection.progress):
+# %S is a message indicating progress of the connection process
+account.connection.progress=Connecting: %S…
+account.connecting=Connecting…
+account.connectedForSeconds=Connected for a few seconds.
+# LOCALIZATION NOTE (account.connectedFor{Double,Single},
+#                    account.reconnectIn{Double,Single}):
+# Each pair of %S is a number followed by a unit. The units are
+# already localized in a downloads.properties file of the toolkit.
+account.connectedForDouble=Connected for %1$S %2$S and %3$S %4$S.
+account.connectedForSingle=Connected for about %1$S %2$S.
+account.reconnectInDouble=Reconnection in %1$S %2$S and %3$S %4$S.
+account.reconnectInSingle=Reconnection in %1$S %2$S.
+
+requestAuthorizeTitle=Authorization request
+# LOCALIZATION NOTE (requestAuthorizeAllow, requestAuthorizeDeny):
+# the & symbol indicates the position of the character that should be
+# used as the accesskey for this button.
+requestAuthorizeAllow=&Allow
+requestAuthorizeDeny=&Deny
+# LOCALIZATION NOTE (requestAuthorizeText):
+# %S is a contact username.
+requestAuthorizeText=%S added you to his/her buddy list, do you want to allow him/her to see you?
+
+accountsManager.notification.button.accessKey=C
+accountsManager.notification.button.label=Connect Now
+accountsManager.notification.userDisabled.label=You have disabled automatic connections.
+accountsManager.notification.safeMode.label=Automatic Connection Settings have been ignored because the application is currently running in Safe-Mode.
+accountsManager.notification.startOffline.label=Automatic Connection Settings have been ignored because the application was started in Offline Mode.
+accountsManager.notification.crash.label=The last run exited unexpectedly while connecting. Automatic Connections have been disabled to give you an opportunity to Edit your Settings.
+# LOCALIZATION NOTE (accountsManager.notification.singleCrash.label): Semi-colon list of plural forms.
+#  See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+#  #1 is the number of accounts that are suspected to have caused a crash.
+accountsManager.notification.singleCrash.label=A previous run exited unexpectedly while connecting a new or edited account. It has not been connected so that you can Edit its Settings.;A previous run exited unexpectedly while connecting #1 new or edited accounts. They have not been connected so that you can Edit their Settings.
+accountsManager.notification.other.label=Automatic connection has been disabled.
new file mode 100644
--- /dev/null
+++ b/mail/locales/en-US/chrome/messenger/joinChat.dtd
@@ -0,0 +1,6 @@
+<!ENTITY joinChatWindow.title           "Join chat">
+<!ENTITY name.label                     "Room">
+<!ENTITY optional.label                 "(optional)">
+<!ENTITY account.label                  "Account">
+<!ENTITY autojoin.label                 "Auto-join this Chat Room">
+<!ENTITY autojoin.accesskey             "A">
--- a/mail/locales/en-US/chrome/messenger/messenger.dtd
+++ b/mail/locales/en-US/chrome/messenger/messenger.dtd
@@ -21,16 +21,18 @@
 <!ENTITY newVirtualFolderCmd.label "Saved Search…">
 <!ENTITY newVirtualFolderCmd.accesskey "S">
 <!ENTITY newOtherAccountsCmd.label "Other Accounts…">
 <!ENTITY newOtherAccountsCmd.accesskey "O">
 <!ENTITY newCreateEmailAccountCmd.label "Get a New Mail Account…">
 <!ENTITY newCreateEmailAccountCmd.accesskey "G">
 <!ENTITY newExistingEmailAccountCmd.label "Existing Mail Account…">
 <!ENTITY newExistingEmailAccountCmd.accesskey "E">
+<!ENTITY newIMAccountCmd.label "Chat Account…">
+<!ENTITY newIMAccountCmd.accesskey "C">
 <!ENTITY openMessageFileCmd.label "Open Saved Message…">
 <!ENTITY openMessageFileCmd.accesskey "O">
 <!ENTITY openAttachmentCmd.label "Attachments">
 <!ENTITY openAttachmentCmd.accesskey "A">
 <!ENTITY saveAsMenu.label "Save As">
 <!ENTITY saveAsMenu.accesskey "S">
 <!ENTITY saveAsFileCmd.label "File">
 <!ENTITY saveAsFileCmd.accesskey "F">
@@ -320,16 +322,19 @@ you can use these alternative items. Oth
 <!ENTITY prevUnreadMsgCmd.accesskey "U">
 <!ENTITY prevUnreadMsgCmd.key "p">
 <!ENTITY goForwardCmd.label "Forward">
 <!ENTITY goForwardCmd.accesskey "F">
 <!ENTITY goForwardCmd.commandKey "]">
 <!ENTITY goBackCmd.label "Back">
 <!ENTITY goBackCmd.accesskey "B">
 <!ENTITY goBackCmd.commandKey "[">
+<!ENTITY goChatCmd.label "Chat">
+<!ENTITY goChatCmd.accesskey "c">
+<!ENTITY goChatCmd.commandKey "C">
 <!ENTITY prevStarredMsgCmd.label "Starred Message">
 <!ENTITY prevStarredMsgCmd.accesskey "S">
 <!ENTITY folderMenu.label "Folder">
 <!ENTITY folderMenu.accesskey "O">
 <!ENTITY thisFolder.label "This Folder">
 <!ENTITY thisFolder.accesskey "F">
 <!ENTITY goRecentlyClosedTabs.label "Recently Closed Tabs">
 <!ENTITY goRecentlyClosedTabs.accesskey "R">
@@ -463,16 +468,22 @@ you can use these alternative items. Oth
 <!ENTITY messengerCmd.accesskey "N">
 <!ENTITY addressBookCmd.label "Address Book">
 <!ENTITY addressBookCmd.accesskey "B">
 <!ENTITY addressBookCmd.key "B">
 <!ENTITY addons.label "Add-ons">
 <!ENTITY addons.accesskey "A">
 <!ENTITY activitymanager.label "Activity Manager">
 <!ENTITY activitymanager.accesskey "v">
+<!ENTITY imAccountsStatus.label "Chat status">
+<!ENTITY imAccountsStatus.accesskey "C">
+<!ENTITY imStatus.available          "Available">
+<!ENTITY imStatus.unavailable        "Unavailable">
+<!ENTITY imStatus.offline            "Offline">
+<!ENTITY imStatus.showAccounts       "Show Accounts…">
 <!ENTITY savedFiles.label "Saved Files">
 <!ENTITY savedFiles.accesskey "l">
 <!ENTITY savedFiles.key "j">
 <!ENTITY filtersCmd.label "Message Filters…">
 <!ENTITY filtersCmd.accesskey "F">
 <!ENTITY filtersApply.label "Run Filters on Folder">
 <!ENTITY filtersApply.accesskey "R">
 <!ENTITY filtersApplyToSelection.label "Run Filters on Selected Messages">
@@ -516,16 +527,17 @@ you can use these alternative items. Oth
 <!ENTITY markButton.label "Mark">
 <!ENTITY printButton.label "Print">
 <!ENTITY stopButton.label "Stop">
 <!ENTITY throbberItem.title "Activity Indicator">
 <!ENTITY junkItem.title "Junk">
 <!ENTITY junkButton.label "Junk">
 <!ENTITY notJunkButton.label "Not Junk">
 <!ENTITY addressBookButton.label "Address Book">
+<!ENTITY chatButton.label "Chat">
 <!ENTITY glodaSearch.title "Global Search">
 <!ENTITY searchItem.title "Quick Search">
 <!ENTITY mailViewsToolbarItem.title "Mail Views">
 <!ENTITY folderLocationToolbarItem.title "Folder Location">
 <!ENTITY tagButton.label "Tag">
 <!ENTITY compactButton.label "Compact">
 
 <!-- Mail Toolbar Tooltips-->
@@ -549,16 +561,17 @@ you can use these alternative items. Oth
 <!ENTITY deleteButton.tooltip "Delete selected message or folder">
 <!ENTITY undeleteButton.tooltip "Undelete selected message">
 <!ENTITY markButton.tooltip "Mark messages">
 <!ENTITY printButton.tooltip "Print this message">
 <!ENTITY stopButton.tooltip "Stop the current transfer">
 <!ENTITY junkButton.tooltip "Mark the selected messages as junk">
 <!ENTITY notJunkButton.tooltip "Mark the selected messages as not junk">
 <!ENTITY addressBookButton.tooltip "Go to the address book">
+<!ENTITY chatButton.tooltip "Show the Chat tab">
 <!ENTITY tagButton.tooltip "Tag messages">
 <!ENTITY compactButton.tooltip "Remove deleted messages from selected folder">
 
 <!-- Tags Menu Popup -->
 <!ENTITY addNewTag.label "New Tag…">
 <!ENTITY addNewTag.accesskey "N">
 
 <!-- Folder Pane -->
new file mode 100644
--- /dev/null
+++ b/mail/locales/en-US/chrome/messenger/preferences/chat.dtd
@@ -0,0 +1,20 @@
+<!ENTITY  startupAction.label         "When &brandShortName; starts:">
+<!ENTITY  startupAction.accesskey     "s">
+<!ENTITY  startupOffline.label        "Keep my Chat Accounts offline">
+<!ENTITY  startupConnectAuto.label    "Connect my chat accounts automatically">
+
+<!-- LOCALIZATION NOTE: reportIdleAfter.label is displayed first, then
+there's a field where the user can enter a number, and itemTime is
+displayed at the end of the line. The translations of the
+reportIdleAfter.label and idleTime parts don't have to mean the exact
+same thing as in English; please try instead to translate the whole
+sentence. -->
+<!ENTITY  reportIdleAfter.label         "Let my contacts know that I am Idle after">
+<!ENTITY  reportIdleAfter.accesskey     "I">
+<!ENTITY  idleTime                      "minutes of inactivity">
+
+<!ENTITY  andSetStatusToAway.label      "and set my status to Away with this status message:">
+<!ENTITY  andSetStatusToAway.accesskey  "A">
+
+<!ENTITY  sendTyping.label              "Send typing notifications in conversations">
+<!ENTITY  sendTyping.accesskey          "t">
--- a/mail/locales/en-US/chrome/messenger/preferences/preferences.dtd
+++ b/mail/locales/en-US/chrome/messenger/preferences/preferences.dtd
@@ -2,11 +2,12 @@
 <!ENTITY  prefWindow.titleGNOME   "&brandShortName; Preferences">
 <!ENTITY  prefWindow.styleWindows "width: 48em; min-height: 38.5em;">
 <!ENTITY  prefWindow.styleMac     "width: 47em;">
 <!ENTITY  prefWindow.styleGNOME   "width: 47em; min-height: 38em;">
 
 <!ENTITY paneGeneral.title        "General">
 <!ENTITY paneDisplay.title        "Display">
 <!ENTITY paneComposition.title    "Composition">
+<!ENTITY paneChat.title           "Chat">
 <!ENTITY paneAttachments.title    "Attachments">
 <!ENTITY paneSecurity.title       "Security">
 <!ENTITY paneAdvanced.title       "Advanced">
--- a/mail/locales/filter.py
+++ b/mail/locales/filter.py
@@ -1,13 +1,13 @@
 def test(mod, path, entity = None):
   import re
-  # ignore anyhting but Thunderbird
+  # ignore anything but Thunderbird
   if mod not in ("netwerk", "dom", "toolkit", "security/manager",
-                 "mail", "editor/ui", "extensions/spellcheck",
+                 "mail", "chat", "editor/ui", "extensions/spellcheck",
                  "other-licenses/branding/thunderbird"):
     return False
 
   # Ignore Lorentz strings, at least temporarily
   if mod == "toolkit" and path == "chrome/mozapps/plugins/plugins.dtd":
     if entity.startswith('reloadPlugin.'): return False
     if entity.startswith('report.'): return False
 
--- a/mail/locales/jar.mn
+++ b/mail/locales/jar.mn
@@ -33,16 +33,17 @@
   locale/@AB_CD@/messenger/am-server-advanced.dtd                       (%chrome/messenger/am-server-advanced.dtd)
   locale/@AB_CD@/messenger/am-copies.dtd                                (%chrome/messenger/am-copies.dtd)
   locale/@AB_CD@/messenger/am-offline.dtd                               (%chrome/messenger/am-offline.dtd)
   locale/@AB_CD@/messenger/am-addressing.dtd                            (%chrome/messenger/am-addressing.dtd)
   locale/@AB_CD@/messenger/am-main.dtd                                  (%chrome/messenger/am-main.dtd)
   locale/@AB_CD@/messenger/am-server-top.dtd                            (%chrome/messenger/am-server-top.dtd)
   locale/@AB_CD@/messenger/am-identities-list.dtd                       (%chrome/messenger/am-identities-list.dtd)
   locale/@AB_CD@/messenger/am-identity-edit.dtd                         (%chrome/messenger/am-identity-edit.dtd)
+  locale/@AB_CD@/messenger/am-im.dtd                                    (%chrome/messenger/am-im.dtd)
   locale/@AB_CD@/messenger/am-serverwithnoidentities.dtd                (%chrome/messenger/am-serverwithnoidentities.dtd)
   locale/@AB_CD@/messenger/am-junk.dtd                                  (%chrome/messenger/am-junk.dtd)
   locale/@AB_CD@/messenger/prefs.properties                             (%chrome/messenger/prefs.properties)
   locale/@AB_CD@/messenger/smtpEditOverlay.dtd                          (%chrome/messenger/smtpEditOverlay.dtd)
   locale/@AB_CD@/messenger/am-smime.dtd                                 (%chrome/messenger/am-smime.dtd)
   locale/@AB_CD@/messenger/am-smime.properties                          (%chrome/messenger/am-smime.properties)
   locale/@AB_CD@/messenger/messenger.properties                         (%chrome/messenger/messenger.properties)
   locale/@AB_CD@/messenger/folderpane.dtd                               (%chrome/messenger/folderpane.dtd)
@@ -132,16 +133,17 @@
   locale/@AB_CD@/messenger/messengercompose/askSendFormat.properties    (%chrome/messenger/messengercompose/askSendFormat.properties)
   locale/@AB_CD@/messenger/messengercompose/sendProgress.dtd            (%chrome/messenger/messengercompose/sendProgress.dtd)
   locale/@AB_CD@/messenger/messengercompose/sendProgress.properties     (%chrome/messenger/messengercompose/sendProgress.properties)
   locale/@AB_CD@/messenger/messengercompose/composeMsgs.properties      (%chrome/messenger/messengercompose/composeMsgs.properties)
   locale/@AB_CD@/messenger/messengercompose/mailComposeEditorOverlay.dtd (%chrome/messenger/messengercompose/mailComposeEditorOverlay.dtd)
   locale/@AB_CD@/messenger/preferences/preferences.dtd                  (%chrome/messenger/preferences/preferences.dtd)
   locale/@AB_CD@/messenger/preferences/general.dtd                      (%chrome/messenger/preferences/general.dtd)
   locale/@AB_CD@/messenger/preferences/display.dtd                      (%chrome/messenger/preferences/display.dtd)
+  locale/@AB_CD@/messenger/preferences/chat.dtd                         (%chrome/messenger/preferences/chat.dtd)
   locale/@AB_CD@/messenger/preferences/compose.dtd                      (%chrome/messenger/preferences/compose.dtd)
   locale/@AB_CD@/messenger/preferences/sendoptions.dtd                  (%chrome/messenger/preferences/sendoptions.dtd)
   locale/@AB_CD@/messenger/preferences/security.dtd                     (%chrome/messenger/preferences/security.dtd)
   locale/@AB_CD@/messenger/preferences/junkLog.dtd                      (%chrome/messenger/preferences/junkLog.dtd)
   locale/@AB_CD@/messenger/preferences/advanced.dtd                     (%chrome/messenger/preferences/advanced.dtd)
   locale/@AB_CD@/messenger/preferences/attachmentReminder.dtd           (%chrome/messenger/preferences/attachmentReminder.dtd)
   locale/@AB_CD@/messenger/preferences/receipts.dtd                     (%chrome/messenger/preferences/receipts.dtd)
   locale/@AB_CD@/messenger/preferences/connection.dtd                   (%chrome/messenger/preferences/connection.dtd)
@@ -162,16 +164,23 @@
   locale/@AB_CD@/messenger/searchIntegrationWin.dtd                     (%chrome/messenger/searchIntegrationWin.dtd)
   locale/@AB_CD@/messenger/searchIntegrationMac.dtd                     (%chrome/messenger/searchIntegrationMac.dtd)
   locale/@AB_CD@/messenger/searchIntegrationDefault.dtd                 (%chrome/messenger/searchIntegrationDefault.dtd)
   locale/@AB_CD@/messenger/activity.dtd                                 (%chrome/messenger/activity.dtd)
   locale/@AB_CD@/messenger/activity.properties                          (%chrome/messenger/activity.properties)
   locale/@AB_CD@/messenger/downloads/settingsChange.dtd                 (%chrome/overrides/settingsChange.dtd)
   locale/@AB_CD@/messenger/netError.dtd                                 (%chrome/overrides/netError.dtd)
   locale/@AB_CD@/messenger/downloadsOverlay.dtd                         (%chrome/messenger/downloadsOverlay.dtd)
+  locale/@AB_CD@/messenger/chat.dtd                                     (%chrome/messenger/chat.dtd)
+  locale/@AB_CD@/messenger/chat.properties                              (%chrome/messenger/chat.properties)
+  locale/@AB_CD@/messenger/addbuddy.dtd                                 (%chrome/messenger/addbuddy.dtd)
+  locale/@AB_CD@/messenger/joinChat.dtd                                 (%chrome/messenger/joinChat.dtd)
+  locale/@AB_CD@/messenger/imAccounts.dtd                               (%chrome/messenger/imAccounts.dtd)
+  locale/@AB_CD@/messenger/imAccounts.properties                        (%chrome/messenger/imAccounts.properties)
+  locale/@AB_CD@/messenger/imAccountWizard.dtd                          (%chrome/messenger/imAccountWizard.dtd)
 % locale messenger-mapi @AB_CD@ %locale/@AB_CD@/messenger-mapi/
   locale/@AB_CD@/messenger-mapi/mapi.properties                         (%chrome/messenger-mapi/mapi.properties)
 % locale messenger-newsblog @AB_CD@ %locale/@AB_CD@/messenger-newsblog/
   locale/@AB_CD@/messenger-newsblog/newsblog.properties                 (%chrome/messenger-newsblog/newsblog.properties)
   locale/@AB_CD@/messenger-newsblog/feed-subscriptions.dtd              (%chrome/messenger-newsblog/feed-subscriptions.dtd)
   locale/@AB_CD@/messenger-newsblog/am-newsblog.dtd                     (%chrome/messenger-newsblog/am-newsblog.dtd)
 % locale messenger-smime @AB_CD@ %locale/@AB_CD@/messenger-smime/
   locale/@AB_CD@/messenger-smime/msgCompSMIMEOverlay.dtd                (%chrome/messenger-smime/msgCompSMIMEOverlay.dtd)
--- a/mail/locales/l10n-aurora.ini
+++ b/mail/locales/l10n-aurora.ini
@@ -1,14 +1,15 @@
 [general]
 depth = ../..
 all = mail/locales/all-locales
 
 [compare]
 dirs = mail
+    chat
     other-licenses/branding/thunderbird
     editor/ui
 
 [includes]
 # non-central apps might want to use %(topsrcdir)s here, or other vars
 # RFE: that needs to be supported by compare-locales, too, though
 toolkit = mozilla/toolkit/locales/l10n.ini
 
--- a/mail/locales/l10n-beta.ini
+++ b/mail/locales/l10n-beta.ini
@@ -1,14 +1,15 @@
 [general]
 depth = ../..
 all = mail/locales/all-locales
 
 [compare]
 dirs = mail
+    chat
     other-licenses/branding/thunderbird
     editor/ui
 
 [includes]
 # non-central apps might want to use %(topsrcdir)s here, or other vars
 # RFE: that needs to be supported by compare-locales, too, though
 toolkit = mozilla/toolkit/locales/l10n.ini
 
--- a/mail/locales/l10n-central.ini
+++ b/mail/locales/l10n-central.ini
@@ -1,14 +1,15 @@
 [general]
 depth = ../..
 all = mail/locales/all-locales
 
 [compare]
 dirs = mail
+    chat
     other-licenses/branding/thunderbird
     editor/ui
 
 [includes]
 # non-central apps might want to use %(topsrcdir)s here, or other vars
 # RFE: that needs to be supported by compare-locales, too, though
 toolkit = mozilla/toolkit/locales/l10n.ini
 
--- a/mail/locales/l10n.ini
+++ b/mail/locales/l10n.ini
@@ -1,14 +1,15 @@
 [general]
 depth = ../..
 all = mail/locales/all-locales
 
 [compare]
 dirs = mail
+    chat
     other-licenses/branding/thunderbird
     editor/ui
 
 [includes]
 # non-central apps might want to use %(topsrcdir)s here, or other vars
 # RFE: that needs to be supported by compare-locales, too, though
 toolkit = mozilla/toolkit/locales/l10n.ini
 
--- a/mail/makefiles.sh
+++ b/mail/makefiles.sh
@@ -40,16 +40,17 @@ add_makefiles "
 mail/Makefile
 mail/app/Makefile
 mail/app/profile/Makefile
 mail/base/Makefile
 mail/components/Makefile
 mail/components/addrbook/Makefile
 mail/components/build/Makefile
 mail/components/compose/Makefile
+mail/components/im/Makefile
 mail/components/phishing/Makefile
 mail/components/preferences/Makefile
 mail/components/shell/Makefile
 mail/components/shell/public/Makefile
 mail/extensions/Makefile
 mail/extensions/mailviews/Makefile
 mail/extensions/smime/Makefile
 mail/installer/Makefile
@@ -58,9 +59,11 @@ mail/locales/Makefile
 mail/test/mozmill/Makefile
 mail/themes/Makefile
 mail/themes/gnomestripe/Makefile
 mail/themes/pinstripe/Makefile
 mail/themes/qute/Makefile
 $MOZ_BRANDING_DIRECTORY/Makefile
 $MOZ_BRANDING_DIRECTORY/locales/Makefile
 "
+
+. "${srcdir}/chat/makefiles.sh"
 fi
--- a/mail/test/mozmill/migration/test-toolbar.js
+++ b/mail/test/mozmill/migration/test-toolbar.js
@@ -41,31 +41,31 @@
 
 var MODULE_NAME = "test-toolbar";
 
 var RELATIVE_ROOT = "../shared-modules";
 var MODULE_REQUIRES = ["folder-display-helpers", "migration-helpers"];
 
 // Use the Windows/Linux settings as the default, but check out setupModule.
 var DEFAULT_TB2_SET = "button-getmsg,button-newmsg,button-address,separator,button-reply,button-replyall,button-replylist,button-forward,separator,button-tag,button-delete,button-junk,button-print,separator,button-goback,button-goforward,spring,gloda-search";
-var DEFAULT_TB3_SET = "button-getmsg,button-newmsg,button-address,separator,button-tag,qfb-show-filter-bar,spring,gloda-search"
+var DEFAULT_TB3_SET = "button-getmsg,button-newmsg,button-chat,button-address,separator,button-tag,qfb-show-filter-bar,spring,gloda-search";
 var CUSTOM_TB3_SET = "button-getmsg,button-newmsg,button-address,spacer,button-tag,spring,folder-location-container,gloda-search,throbber-box";
 var DEFAULT_TB3_ICONSIZE = "large";
 
 
 function setupModule(module) {
   let fdh = collector.getModule("folder-display-helpers");
   fdh.installInto(module);
   let mh = collector.getModule("migration-helpers");
   mh.installInto(module);
 
   // The Mac has different settings for the toolbar, so adjust for that.
   if (Application.platformIsMac) {
     DEFAULT_TB2_SET = "button-getmsg,button-newmsg,button-address,spacer,button-reply,button-replyall,button-replylist,button-forward,spacer,button-tag,button-delete,button-junk,button-print,spacer,button-goback,button-goforward,spring,gloda-search,throbber-box";
-    DEFAULT_TB3_SET = "button-getmsg,button-newmsg,button-address,spacer,button-tag,qfb-show-filter-bar,spring,gloda-search";
+    DEFAULT_TB3_SET = "button-getmsg,button-newmsg,button-chat,button-address,spacer,button-tag,qfb-show-filter-bar,spring,gloda-search";
     DEFAULT_TB3_ICONSIZE = "small";
   }
 }
 
 /**
  * Assert that the settings correspond to the default TB2 settings.
  *
  * @param aNewBar the mail-bar3 to check.
--- a/mail/test/xpcshell.ini
+++ b/mail/test/xpcshell.ini
@@ -22,10 +22,12 @@ skip-if.os = mac
 [include:ldap/xpcom/tests/unit/xpcshell.ini]
 
 # XXX Calendar tests are skipped until we can test for
 # Lightning being built, but we need this line here so
 # that Lightning can be built with tests in its directory.
 [include:calendar/test/unit/xpcshell.ini]
 skip-if = true
 
+[include:chat/protocols/irc/test/xpcshell.ini]
+
 # Include all the core tests.
 [include:xpcshell-core.ini]
--- a/mail/themes/gnomestripe/jar.mn
+++ b/mail/themes/gnomestripe/jar.mn
@@ -8,16 +8,28 @@ classic.jar:
   skin/classic/messenger/featureConfigurators/folder-columns.png    (mail/featureConfigurators/folder-columns.png)
   skin/classic/messenger/featureConfigurators/toolbars.png    (mail/featureConfigurators/toolbars.png)
   skin/classic/messenger/primaryToolbar.css                   (mail/primaryToolbar.css)
   skin/classic/messenger/aboutSupport.css                     (../qute/mail/aboutSupport.css)
   skin/classic/messenger/accountCentral.css                   (mail/accountCentral.css)
   skin/classic/messenger/accountCreation.css                  (mail/accountCreation.css)
   skin/classic/messenger/accountManage.css                    (mail/accountManage.css)
   skin/classic/messenger/accountWizard.css                    (mail/accountWizard.css)
+* skin/classic/messenger/chat.css                             (mail/chat.css)
+  skin/classic/messenger/userIcon.png                         (mail/userIcon.png)
+* skin/classic/messenger/imAccounts.css                       (../../components/im/themes/imAccounts.css)
+* skin/classic/messenger/imAccountWizard.css                  (../../components/im/themes/imAccountWizard.css)
+  skin/classic/messenger/imBuddytooltip.css                   (../../components/im/themes/imBuddytooltip.css)
+  skin/classic/messenger/imMenulist.css                       (../../components/im/themes/imMenulist.css)
+* skin/classic/messenger/imRichlistbox.css                    (../../components/im/themes/imRichlistbox.css)
+  skin/classic/messenger/imStatus.css                         (../../components/im/themes/imStatus.css)
+  skin/classic/messenger/founder.png                          (../../components/im/themes/founder.png)
+  skin/classic/messenger/operator.png                         (../../components/im/themes/operator.png)
+  skin/classic/messenger/half-operator.png                    (../../components/im/themes/half-operator.png)
+  skin/classic/messenger/voice.png                            (../../components/im/themes/voice.png)
   skin/classic/messenger/browserRequest.css                   (mail/browserRequest.css)
   skin/classic/messenger/section_collapsed.png                (mail/section_collapsed.png)
   skin/classic/messenger/section_expanded.png                 (mail/section_expanded.png)
   skin/classic/messenger/messageHeader.css                    (mail/messageHeader.css)
   skin/classic/messenger/messageBody.css                      (mail/messageBody.css)
   skin/classic/messenger/webSearch.css                        (mail/webSearch.css)
   skin/classic/messenger/messageQuotes.css                    (mail/messageQuotes.css)
   skin/classic/messenger/messenger.css                        (mail/messenger.css)
@@ -92,16 +104,17 @@ classic.jar:
   skin/classic/messenger/preferences/preferences.css          (mail/preferences/preferences.css)
   skin/classic/messenger/preferences/general.png              (mail/preferences/general.png)
   skin/classic/messenger/preferences/display.png              (mail/preferences/display.png)
   skin/classic/messenger/preferences/composition.png          (mail/preferences/composition.png)
   skin/classic/messenger/preferences/security.png             (mail/preferences/security.png)
   skin/classic/messenger/preferences/attachments.png          (mail/preferences/attachments.png)
   skin/classic/messenger/preferences/applications.css         (mail/preferences/applications.css)
   skin/classic/messenger/preferences/advanced.png             (mail/preferences/advanced.png)
+  skin/classic/messenger/preferences/chat.png                 (mail/preferences/chat.png)
   skin/classic/messenger/preferences/background.png           (mail/preferences/background.png)
   skin/classic/messenger/preferences/hover.png                (mail/preferences/hover.png)
   skin/classic/messenger/preferences/selected.png             (mail/preferences/selected.png)
   skin/classic/messenger/preferences/auth-error.png           (mail/preferences/auth-error.png)
   skin/classic/messenger/smime/msgCompSMIMEOverlay.css        (mail/smime/msgCompSMIMEOverlay.css)
   skin/classic/messenger/smime/msgHdrViewSMIMEOverlay.css     (mail/smime/msgHdrViewSMIMEOverlay.css)
   skin/classic/messenger/smime/msgReadSMIMEOverlay.css        (mail/smime/msgReadSMIMEOverlay.css)
   skin/classic/messenger/smime/msgReadSecurityInfo.css        (mail/smime/msgReadSecurityInfo.css)
@@ -167,16 +180,20 @@ classic.jar:
   skin/classic/messenger/icons/tabDragIndicator.png           (mail/icons/tabDragIndicator.png)
   skin/classic/messenger/icons/button-archive.svg             (mail/icons/button-archive.svg)
   skin/classic/messenger/icons/button-delete.svg              (mail/icons/button-delete.svg)
   skin/classic/messenger/icons/button-forward.svg             (mail/icons/button-forward.svg)
   skin/classic/messenger/icons/button-junk.svg                (mail/icons/button-junk.svg)
   skin/classic/messenger/icons/button-reply.svg               (mail/icons/button-reply.svg)
   skin/classic/messenger/icons/button-reply-all.svg           (mail/icons/button-reply-all.svg)
   skin/classic/messenger/icons/button-reply-list.svg          (mail/icons/button-reply-list.svg)
+  skin/classic/messenger/icons/chat-toolbar.png               (mail/icons/chat-toolbar.png)
+  skin/classic/messenger/icons/chat-toolbar-small.png         (mail/icons/chat-toolbar-small.png)
+  skin/classic/messenger/icons/status.png                     (mail/icons/status.png)
+  skin/classic/messenger/icons/status-small.png               (mail/icons/status-small.png)
 % skin communicator classic/1.0 %skin/classic/communicator/
   skin/classic/communicator/communicator.css                      (mail/communicator.css)
   skin/classic/communicator/icons/smileys/smiley-smile.png        (mail/icons/smiley-smile.png)
   skin/classic/communicator/icons/smileys/smiley-frown.png        (mail/icons/smiley-frown.png)
   skin/classic/communicator/icons/smileys/smiley-wink.png         (mail/icons/smiley-wink.png)
   skin/classic/communicator/icons/smileys/smiley-tongue-out.png  (mail/icons/smiley-tongue-out.png)
   skin/classic/communicator/icons/smileys/smiley-laughing.png (mail/icons/smiley-laughing.png)
   skin/classic/communicator/icons/smileys/smiley-embarassed.png  (mail/icons/smiley-embarassed.png)
new file mode 100644
--- /dev/null
+++ b/mail/themes/gnomestripe/mail/chat.css
@@ -0,0 +1,101 @@
+%include ../../../components/im/themes/chat.css
+
+/* Adaptation of #folderpane_splitter -> #listSplitter, #threadpane-splitter -> #contextSplitter */
+#listSplitter, #contextSplitter {
+  -moz-appearance: none;
+  border-left: 1px solid ThreeDShadow;
+  /* splitter grip area */
+  width: 5px;
+  margin-top: 0;
+  /* make only the splitter border visible */
+  -moz-margin-end: -5px;
+  /* because of the negative margin needed to make the splitter visible */
+  position: relative;
+  z-index: 10;
+  -moz-transition: border-width .3s ease-in;
+}
+
+/* Adaptation from #folderTree */
+#listPaneBox {
+  background-color: -moz-OddTreeRow;
+}
+
+#listPaneBox > * {
+  background: transparent !important;
+  -moz-appearance: none !important;
+  border: none;
+}
+
+#button-add-buddy {
+  list-style-image: url("chrome://messenger/skin/icons/chat-toolbar.png");
+  -moz-image-region: rect(0px 22px 22px 0px);
+}
+
+#button-add-buddy[disabled] {
+  list-style-image: url("chrome://messenger/skin/icons/chat-toolbar.png");
+  -moz-image-region: rect(22px 22px 44px 0px);
+}
+
+#button-join-chat {
+  list-style-image: url("chrome://messenger/skin/icons/chat-toolbar.png");
+  -moz-image-region: rect(0px 44px 22px 22px);
+}
+
+#button-join-chat[disabled] {
+  list-style-image: url("chrome://messenger/skin/icons/chat-toolbar.png");
+  -moz-image-region: rect(22px 44px 44px 22px);
+}
+
+toolbar[iconsize="small"] #button-add-buddy {
+  list-style-image: url("chrome://messenger/skin/icons/chat-toolbar-small.png");
+  -moz-image-region: rect(0px 16px 16px 0px);
+}
+
+
+toolbar[iconsize="small"] #button-add-buddy[disabled] {
+  list-style-image: url("chrome://messenger/skin/icons/chat-toolbar-small.png");
+  -moz-image-region: rect(16px 16px 32px 0px);
+}
+
+toolbar[iconsize="small"] #button-join-chat {
+  list-style-image: url("chrome://messenger/skin/icons/chat-toolbar-small.png");
+  -moz-image-region: rect(0px 32px 16px 16px);
+}
+
+toolbar[iconsize="small"] #button-join-chat[disabled] {
+  list-style-image: url("chrome://messenger/skin/icons/chat-toolbar-small.png");
+  -moz-image-region: rect(16px 32px 32px 16px);
+}
+
+#statusTypeIcon[status="available"],
+#statusTypeAvailable,
+.statusTypeIcon[status="available"],
+#imStatusAvailable {
+  list-style-image: url("chrome://messenger/skin/icons/status.png");
+  -moz-image-region: rect(0px 16px 16px 0px);
+}
+
+#statusTypeIcon[status="idle"],
+.statusTypeIcon[status="idle"] {
+  list-style-image: url("chrome://messenger/skin/icons/status.png");
+  -moz-image-region: rect(0px 32px 16px 16px);
+}
+
+#statusTypeIcon[status="offline"],
+#statusTypeIcon[status="invisible"],
+#statusTypeOffline,
+.statusTypeIcon[status="offline"],
+.statusTypeIcon[status="invisible"],
+#imStatusOffline {
+  list-style-image: url("chrome://messenger/skin/icons/status.png");
+  -moz-image-region: rect(0px 48px 16px 32px);
+}
+
+#statusTypeIcon[status="unavailable"],
+#statusTypeIcon[status="away"],
+#statusTypeUnavailable,
+.statusTypeIcon[status="unavailable"],
+.statusTypeIcon[status="away"] {
+  list-style-image: url("chrome://messenger/skin/icons/status.png");
+  -moz-image-region: rect(0px 64px 16px 48px);
+}
\ No newline at end of file
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..6a9d32853b510bc844428f5794a3090485868594
GIT binary patch
literal 1792
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..17afe2394d056905da0417c291cd011d09277c76
GIT binary patch
literal 2812
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
O0000<MNUMnLSTZKKuT5s
index 993a9632a92109dcd79a3167015b179be1f6af55..55d9535260acd9c4a586650cb1ccb6e65ac0b99a
GIT binary patch
literal 14338
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
YN}0qqvK4~3?E?Zn6l7GS0r(949~IKAC;$Ke
index dbae6f1757a8865cc9f085c69aeeb1fb98f7f2fa..b7e47cd40955a755dd6e0ff5d7da28f92365010d
GIT binary patch
literal 23395
zc${Q=Wl&pfv~_~JyIX-$DDLi1C{nDr7mB;PTY(n$0Hx4i#T|mXySuwPxp}|&?ys9n
zPBPCqb7r6YthM&qGdt00D)N|UWM}{Y08>#x<_iD-w1)lWKtX~XMN~JQVP8mQO7b!Q
z=znieTX7O>1=UGG*A)O@!ujt5O#ZG;3R^^WQ~WH8yaL2SCwxcMO1TAFB6pMh>L%@I
zZ*S?~2Ka3H-Hyi1(b3M#)P?4|xub*YN1(#=B>+GJP?V9>^jbRc_e|H6y}Q%rcRL&O
zP8Z>@VECk_<Ay6Mfsww-8`5m+A#3szS0=<3f`Nn))EuRx0(V45LnAo_8Q};XY;tYP
zKYA{N$M=Z<%D$djiH=)lo*hlQtLFHO>lcre!@V&FCPVY3ie)d{^m@|8c`oH2jhUsa
zSbi9nO~TQXuN!kKUcKaZ8CS3d+}{fR;Crsk`%qk4VsR&Y-hRIVjoervhA;uvUb-_s
z8tB$q{}lK>#So6u6@cnpxz-mB-<U_g<1D9uV0v@gpE`XQXf1?rP!B)x3u4>2cm5vS
z13!_6t`OK}-#tx}-urbwpg~p|m88&X8!VoXF=dpqocn6Qwb|%7u0sk2`@QwPv_Rn`
zfRKRGvGfKLtgNeqydUG~(5YCjyNtt~`8lW~%7|XjDAGU@;2OY?>N~mh)>e0YY)_v1
zC$7!ytuUpP^J$1P;Z6Ih<7NB`HEi1mIgu5O?x#ic`xqP3xYJ9o6@AMX_oO$W_fUht
zySnA3rs%(-c>_%ccx>`*3)kzsDv@=E?Mz~Y#xKB$9On79;uNdZr*H^9_2ynw(BI)o
z4N~(zuE2v){rQ>?A3M-Q`aQ46S)+b|!CO1~frUSJmlM1*S#1q|+%5XAr?m!WHmK@9
zeJ3bh5j<S8-MnCSYP3{{fh+AM2arH9lZZSiy$){_2E^;h#tlT_V`6$QUpXG<zctqE
z?^JW@BuKBf`%9t(gTW>zad{?j!4HnVZ=XfjySAP4|NX-u=PU1xJ8arXn}ep7lM|%T
z^sa~;eLFt4M3N|$-2vE@4$-1%&RyWFZy42zJ^E7E89T0TG`~!#`$*!9J+`oDRXrYK
z&VcMW;wiin_UBVOwn~f4r>D6(PNsc>QuoIfu!{&B8qhI6V;;S|KL1{7&P+QhWvg*@
zbxj$2yCe7fe$3u+7HsXi`OUf;Px5wf;r5nTE1C8*dc5Q%5U8LNaQhC>gC4?>^~Hy{
zNYhiyMIhc4S4vD%%YE+$|H_P)is+;Jw8+3nXw@WpcA#=NG0g`TR}5<)lGui+$p{qd
zx@~SD7H40mH{AFsO^jDxe4t<!(+8l+i|@Z5R@Kd16sPfyv}zM+M~4Tpb&w5>O5lP6
zbeUI%);ZiVfY>DXmrkYwXd{H-CCCB4EJ@*!Fd)J3$P<;u;%c8lLxXVOq)<rAB|+cf
zg1?~U?sr~8QrWVcra$wW-!4@KIKPtLATh^)W2mYOiv#j+@!_($f%O`EB=nQ_aw;#I
z_}CIg&uv&{eG*>5;)Q{8BZ$>C$9bGXpFRbUKW5X63=JL@l~?>#yL)Sl)L{q@+FnSc
zm7WP}WS|xM7@WuPCg(3H`8LD0%(p7$+j&xICBnU}S)5(QqNJ$~xg-++W6{^E>hv&K
zh3ll`Wxl+*G84*-YC2}z6c5H;)<B+8=HGZftM6nafBBqMrBjD|7E%WQ_|H4z;Im^P
z=Ki9I8W(?jm}ZYiH-&?s3$D7H9WK?8k2|k;k8NpnU$&ZK@9pmk1C$AcP;9M*JO9;f
z6F;Bb8l=~P!OScyA^qFWF@qK|pFSOt_Vtn2@d*>iBC(@zWX1h+%#FAJ;?uC~azy-%
zV3ew`p$wm}siT0Ouy&Skv_6Cg=m!6-#u6&Umnllb6k13!r^&*|(avQZ7I4~tjDKsa
zW^JHfmlyK6JBg+ae8CMx!VH-|#td<B^$w<sp+gAnj=(&2p4HN6NSyxpw#;D}LG<MK
zN*q)vzVSACGmVz50jSp}YE*b@o)^@6p#&^65wjI%%^m_Jwv1}CpI$*c5?V`k4qZ-i
zz4^D)6SIp?w^Bn^$+t66dM9tDOpc+#E?4DA<w~ioWL5sl^?l0@ZTP=V!pjr}#>R*=
zzA##{KR&!lbAMBQy*^oVy?2xPKux*WvU>@nub6BBV|9A?us7d#%uE~z-TsoZ`Uc+G
zRm9^0>_91IKg5W*PQ7B=bam4&`PG|smPdVl<_3VqO8a|oWV&tYtcg0HXUYJ3fZi_#
zKx2*Vyn{#)K}<om4!K~wbi$cvcx&r6wj0U>;6#MUDfuslbr%W{8I8J7BO?w)nw7Pp
z87clk?Qd6tahag4h>!_NGFaiW5+;{SJdPT;o`bA#5XS=WsZ)+)5<wk(;>f&2<jY|J
zi*HGtUycd_WaCbOtu3p7uKFKE#RAGg(l$T}RI`BV4Tk%yYe<uIlY?}CKIjCW17;>@
z^S$re?2epl@jM67+m_4c?&nXU2)Hp&cSrm{X{R0%aB+2Y*V58jXtG7N(6teVjD0N~
zCxHOUwIiRJeR4U}AIfj0F}zSA^+Phzdlk9;xUO&$npj9Dj_%w|fC|tw-N!wnT7uU*
zjLgS=Rqp;?y@+392OSyH7Lq}apLZM=E}G}w;RXkH?s_B*2Ikly2Kk<OcU>JFv*Su_
zc(x)qR3cMsavZ)zD#wa@JuesMdcV}C5x3TZyWcF**VGI{D6_<Tx3-2Wr!UuhvMW}D
zOUkTQ)3d01Dk^oBhE*ufpIreyZ!eFATifQmb(pi(wR6w*DuD6J4R5MjEyRJdEG)o)
zut8MBA0f3*`Ct}Ju#^^I2{<M!3}N);@w!DhgR4jcLE=~--Zs)C+g>piVK7OqFqlD6
z4%E$xMW;;Okesf}$UG8@H;%2Af&Fl`x_mj~?syG1DRl=<Z@OX}_$7aU0YM~lesUwF
zP(cPjta)g7d3l2nM_a|<39ws*jU{JxvEebrK^{=7a85h&e+26dsg;v9Fl6s}exAj2
z>J5|TJHYWTAc)(CC~=((I&)~v<^R>UVFEsNaj!(=C|#ZxxpvxI(O%N2yxTQ-*u^(#
zzCu;|M2kX!5Fe?NYRX4m<+`tOb2we;8v8w9Rjp5o99vr7NlY4)|Anw$MT0)*Y25`0
zU_wXFnqhFqJl@Jz(z_lk0G5=vtFPX)1TfMS42ub2!o$e-juQ2CFUt`@@peTL#UGAF
z5Fm`|ROzg|q!8`JAEf`H)v=NOg8MGh7VzYC-Aq@kFXXbLP(=S)o2y&<t;yclGhWh|
z`O(L}aLEGQDqzuWvcP*YiAmhn|8`b~o;ig?0!3Z`M~X%f?}U$hYa8!k0zTnj4eE5c
zpCEE~@tdEh{Z-NTG+>K+2%zkH%QqZMnU&^`mt#H^*-OTGG<4j_%`|dM=-Op-^(YkI
z%AGnTKZXo?pDbKZXg0xH<2x{JkD0;|d3)PYA>n3WH{jDxf32pj?u{Ju$mX^lM}T;9
zc-feBotr3IJpCxmFak}hrO~C8ye&%G7j^X1o>(k(+=hCux-{}?2^_8wy|fOr;p6(@
znN7fR>#W65jp@%a*Db%?dfhqZaM6J2nw0Rqa$~RiEsbW<B8onUxgB;xd_U0WP)izC
z8UA5Ge-tKD57WP`j~=#W!xCsIqAO^`i{JT*qZMA&jGL=~-!)_tay#<J%&%3Lx*v-K
z&={Z<fesj)>$YBM!q&M_jhyEpnK2LT-h=SlibLG}`ytta8_NAsk&gBD(%K%{<bp<(
z4LRX7M~>9?DiuOEcNh}h<>aehZb2-)fAU1cEewPmNzLs%1|$B?8Ec3GHhdVf@0o8b
zEBf=|vc=G@=DBI&y)e*FEK-ViDv#9{RDzm%^F8H<j=!C{h~np3el>BA{1#1Ki{|c6
z`3<XshxZ*NC#Q2sY2~SBbD3B4^mk8rS((EYhT_f--s-x#bL*S6<2>Y}Pjl|49?Nay
ze0C(f-$qCr^`C!++>Mi<F9JuUKb57-i;zpeFhVcC$3o5OU7X65IaBN@?tQ*Sok`UC
zLih7;7A6&sjEk?Kp8AADs>OmmpNyPrs1pJP2KEZSZj1G0OYB*e8}_5g<*x)`n?Nrt
zQHczTABP3Aw`3Esjl*}_7wa*gzm^9X6XD`|T5lU$hYQFXHHtOkY*e2G+!B5yNM0UF
zp|k=daUj=p2$De%bd-e8l2Vmb?H(sWXlcu+vg)7`Gy>UR@Q@k-B5L%dfX|6_-rYao
z=+;XXpS=xny8qp{>B&OX+c{2P6g`b9Cg#L7(TL(?RHhYG(`%kz#x2rLMHAU{KYK6E
zwei3CH<&>tv2I6H=RxM~XeL#Sdmw4sdg}>%oK3qQ)92VPx5~>1Xf=el=>rW94Q;py
zEpLTFG1cj5=vA<N{3z+<UnxsgBBp(@r8a9Fv7Z(+h^p~XE>W$+Ct4XoV?<=q$a60~
zP6(`m;IM;&esZS}x_ykR0CS<x4vdWS<V#0#IBiG>P{h-8vk9z5jq_3Wg)mk0j^zle
zAJ24BkpB33$r_RqzOjW2k~32yJKn6*32j(NEU2Ov_e3d?!%pQP;bA0yxfmQcOAT#o
zB=Kq+(lr|aB9%W$W0iFu>Rdx-`MfWWhNN1rWq6p2OeDgzyTw@sZ76~|YKy7&m}Oa3
zBYzRr;E?0TMlAsUS*)N*Zm2yt3D6avO6%Rf?eo9`0F}!9xbc~or1YQ@D9#2+dJEP>
z2F-h(7A-^J$%DGg1qYcQ2YC52DWXf6j{wM>V}o)bXu4b3G$Qzw9SJ44msWCj^kA|L
z`wBh&7#Na-*zb=%SAbH;sVr_W@z2Dt*Z3Y1+?FO}X}b;P{jjG@B7WJMI=wN6kygk3
z^SfxFY3F5kzCV3);+3WB=)I4KVn#+r=13VU4z)RBGFVSm-K0(rm_jGSh^6=APFWz9
zLpk>bbY@)HdUIy)xz^=he-ryIf2Ttno}T1i`@ZbrpX3n&v&FQ8YNp}Dl31Cr$)Lhn
z%c%4j!wn4z$9c_^aea|cQK9dA?I&fc);vqcQyN^j#KhDXZM?{uZ0EgI{>Oe{@o6*{
za9%1wM8S#K>?6g~Bs4Kw2z?~qbCK6XL<ndkw)=jF+gfjrC^O-_?Y{UgV1+VLcsm_T
z<QH^1$QxFn^E17oyK1jTf1hXT?cM8;vc#jaW^{qUPbW+?dPxLvwZH;{Tu8aad-#t$
zP{eBv2U+W9dh&t|Cp}K|Wl<-tRM4r0$(b-wj|bdXf#^Ryln)B+o5qkUm0s77PaA?b
zvWVN>7e=IdrTAf-*7jHmdt>Z)Na^2cJcgVps;jFny3Y(rgzN-ROa+WIL&@_VDK;7H
z6E_`n(;{nY83<D5w!unW%a)B)@`z+bu^Y|{pcr)&SK<66mPA1_9y%Y4P@Bq-ns%z&
zNNjq&Z3#aYcE7ss)BytDyT#9+@c|p9qrR69;x3nS<ma{VlD?@Qp(w=Iq!G=1OZBBM
z<u}*>UQ89`XA^jekBI-Rhel$R0B{Ju6O`JNHPj*<>^%jG;Xc()Dk!!i2=&5IWJX`a
zQlZS*`#5u`D1Qy1f@eRy!GQF~9MbqpRijqJ|4Rw7t~L2Z*n@+Vp!}>QYR1FZzUlUK
zj78W@-&S_kD5-E*;i;LTQt8vvNOOtFWJ?NPH0|SYZVyTh;gZmL@0BZE{yBKGh=!FF
zE<Btmf544QLQ>#rD<~EzlnY7Aa>E<T{jGVD(^)}jl;dQj&0O!emq9nR^QER2LWA|0
zYl&1mCd4Mcx*KOJ{MWfh3-&@;{qU_ErGW#nKs<cE^M)W5RwU)FW$|R4ttWFef|j}o
zUt~@dBAm$Uj<}nvQTFZJspskt8}#NHdx<svGQBU#x{RekpghYK^Txb*LX?k`O1Eos
z$>lfOLBxZ)A&4KFOOL$t$Maju;r6>4o7|t4KF_}wnMG{9zWnO?%*d^>mnWgpK%qAD
zA~AZg*-s66e@8T<`7A;dJJ>Z`*^YpORI?cOeC`d<D+l(+Q_IWwATeuUj*CNEi3GY2
zl5uFU0u6+E%v#xNJsF=vYf<`mko_tMeTh&SBe}V%AMlw}A={T-3izBKZTO4~{F!7_
zYpJsxJKr^T(0>^W89t5BE|qrq!nNsrR^g4lcQB4L5yz>StONQ7!YM{m%|Fom31#W~
zTR@jBq!k8+KhD<!gAzUiSP@GE41z;G;wNVwbmrESn>CmcF~Z*v2~%9EM+vH<_2J@t
zGA*+SYEwFC`VB!KZeuP=v?O(CAa@=_UATS4@~d}2?-P4MZTH*#_?6Pg!gE;CPYNzT
z#3)*>lsM~-z+=FvjmD{^Tx|stR3YS*3I6HYd&^;UA3Cjv?(0um>x{}FcdUNjJH6ly
zB2evkg>PSTWmMc$lVmGXLCx}{Fwg&E8bXszY9N9wi9-`ZC!-!FHzP3<g(Hi3@{tgG
zY&DMdm5Uao-KX6H-++$>zJTUm{Ec)OQ~umZgu{GJP${l^alP66y>Qj>a}FTr&DE1W
zU{-WO&6lZKIrFxt0Bf_$%v(DTIf@MD2a@#=GIbi}pLtZHa8XP^FZNNij^*^jf>)bf
zHD<OPe1>zj{GSY3De?0Y@kVT<P#`Z6KiLW15V?VqB8yEK8jl{E9bXF>F4655nT4t}
zJMB_49x`?F%i0osTP+i=tbg9aa$cgjo=Yz(r4ye`HfQFkMZDs%LyV4x@BK-cPa$IL
zAiswGwr|j2>b7qhjp8DZ(0v2}O049x961jUEM-P+f!xSd)6i~HNBFPPe?}+zW%^qK
zw#)gkY}lY{1_nwQ*^^&&0+X(OcK@U#O&}{Kl${BQ!}d-`N|AD=3@iRF*fmOIAbLsL
z{jSiF^!IcYcAI#Tva!haiK?t^onBa^{6>~z2kKb8mX@rR<-4<%dte+HFB-jkd{3L_
zdIA%bFPZ0x2NSpyoIg!^u^R7a%9jP2wDDtL>{&nys&yO<-#V`B=!?K4uQKgMg4W@Z
z^FLKUnw25y9CG?)vj}EO<w>`Fw@0%|=tu;)LWqi2UB{PQC5&4v-Qt&9nJpJ9Ls=Zk
zemLqp<VJ(&mGkgg>Jp^gkl;{6Ktug`{w`?q_wPR-`Eja)#Tu&%$8jUk$x<uTo1;le
zR9Pil!2=)Ap#T|nAofQta>7mj62dvGD#4ksuAd~B8kV9Y<N5WDL==iuCFd#85xJyl
zbyi>KxcRf3yEQlNbSr99lt|1=%F06)?d0Urcu-8E)7y8L{9d+l{<Zj*D=5ZyUT!nd
zmSXhss%XwF2_@^tpg`z5*{=Wb=gMXGNBm2!xM-%tDQt#oC5oSJ$yZd84shl@8#2Uq
zLn>BzlxVKwwpN9XlB!*>pOcqirNup4RZ4eI{XJL~eMh3>;caOxqcZb7tma6b&r_cB
z5V7u%#zYV(i|BKA_9M5=_nCZK2CBQPE?OhbsuiB&@rx?_cI(E6Lx(b|1So?ET7Ver
zqMUP2Y^Wb{i?7nV-F~?vnng)FaHr}zN(i2#hMzwCE1%z{KTP4jn;y7u8h^T*LTFvR
zsQ%SbUaOFtFAamrNK;#@>(K3lRM6!&my1P(nWsPi^LsW)^Qh+HVU_MPC%Zg1XgAb4
z0fe)qRJ^4i0fm7V{U~o+famMGpobvdM2!!_V*M%p-OGO|acPXD5G6xr$-lH0aDAn$
zHulhe%pa<?%6&e2sG^5{WdEQ6Gb=ESRw;(Jv@AMU;zh1z?cBbTD4nI{;sRqTXzUH|
zWTXf$n@k*bv_7A;EQ<_WN_-rmP7kk)4%1PY(Nb51lFZ#|AZ9WYWlWo@waH7KLM3g$
zKUbXYHO<FlbyVi9Ym14;Av?X?AbU@v&nN|j1wV6BgE7hHC2?e&D3FV%qr1glqbk2)
z^k(X&ZQMTp;_xj1zAKL1bQ7wAF}0_^kDi5vWd@;*GU!|5(@`m>!^+n<a(*K7exwRp
zPLGqi0Es_rK|&YMy|`9H33NE9TSxorZEulmR1`{oqNFtE$<`(b=Lue1%ZZ8zx8FM|
z$}3cdwk=!(K^Fo!LqW1ac|-Vwe$2}LSSmqL9+7cz|9V-e_-!>Ut=SO4dCmE!xTtMO
z)v^$h47eZ6lJ*1AmhFg&frcmC632p5R)84E$Ui3^CwP}|JRb<Qu=nVnye<VR5s;Ap
zMFd!B@S@GTLG+NbP{c<2C!MbXuisAC%R<WrqJHoc9Eb5=_UQYX@*Jug*vcw?cSjc-
z9oFJE3@Fa$Bws&qmrz2H`GI=7vEj_ysqaftA0wSerWz6=o%;?+b(zY369Wl@B<SMe
z0xL(-^_9OP6WgpIZ7X=u_Gb=JLiWh-jc396j<EX<%cWpZjpVyOgZVeE1sD?BAHzri
z%f6EWLc(Ot6#xQkwI<5fV*ltq!BD**H+k=gkI}uTWSBI<07(Q~-gb`2I)IrUOjx|s
zQ)ZJ``d4!nMAuOMQibgUba)vWD5)|?r6Zt}W4qqo{UB35j#<=}VRRr?*fkNsPF_&=
z*$c<P#pzfA7R3uLnGK@G*bL@&meWqLw9~@ru<(?s{;R8`Xle3B;VqL>_l&#ANJAol
zNh6hFpOyUfH+TUbzQ<%Z{9?y0d2*GYFWJEF@sY#Q&aXAc7rk?EI8q^3<EeecET03R
zFYCjs6kZ%?$$1|o=POomzr_bJTRUy-a0+B&p@bYDfA>{N{6oN4L%Ll-NxbhQ>|l59
z+4n`L#e+qoxx<@x6Ei~FJSzwXpP(0AArjQ>fzeXd5%6NDf!}i1{y><!Z?l#cO&V)O
zvy<dU(N8NzOGQem`?VneQ$`)WcsjhN#{wlJDAJ(~4tv!H5dZ<rcOSaZiirU)Q7<L!
zuq>hWz$Xl~ye4bzxs@x?pgNNY`)Ywbw)7-YN=ji4){!_Z+q-;y5B{0i**axZNeVZK
ztb1NEN&;OeA0MQt>8TpnwXTyj<I3VQ6+m39_l93WG&rQ9#P1XVgIMPk4Y<Nj&Y(A6
z&GTm<&$Ub91C?5G9j!uOR4q-;pYltx+Z>f*TYjy`DbUp~{3LZ_orIU^AC10qdabW3
zYyyVILEw^yza^;mt1gIOHV-dR0<IvWEMg&}@3h5&0`L#X7*3!bb@Vy`<j-}!wDR&a
zEd_W>jmiR_I^Z3nx=pRHU+h2oO_1KG#7fxx#$DXrqZj;0@e?<KXbjZLGrQY`?d#FG
z>&(l|gBKU7h}PR`>8%izAe(T)K%o7VvqYXQw>6Nb)d<z7GVF;lE499chw_s+1u{en
z&V0$8!$s&fCJ(^0E%=DC-7Bk&<khOIuX8ap=un(a#LtBMwbo>UYnN0*0u+P-M-vsq
z4Cf@}P9>H00Xe!)1m6ZLdxn2j04Ik4Nsd%P%ga(ulnXPXS2E>jd}!DnTbObT`2O&4
zAnu2py}jri6^n$S;j5&USsvo;L9*C~x-_v9w+iZsa$jr<#=gRWYO>`7DoHKNS;@%3
zSKR|vbiy7-=%_Rf@Oj<8jI3sD9aCik#KJn@1T;BbGh=<8?1_v#hORB_5REbB(PMzS
z{yjaL`Q4trA8B=Dj<{94@*i^rC7T}3GhO!1`H6^s7x>u5jPZ-r-5T1|<hVc;_~NL;
z=Jz^FsVeqXo3DBv3z|HFI8zi96m&uo(>w`8eQqKKt)<-DFv!UKzM67n_;1LPMqp9?
zF>Z6XeFsRbyVs{ka7inqO`7kKyd-#r3}a7G>;sv8Lr%*=CRu~8-=drh5k5X%*G1&M
zMw*Xag--*?TvYL7wIHq(&2EYjtixYh0(i4ki1D`Y@|n@r9{qg(IoP>TWUDa2P9u$9
ztG%FmvLfKxovy%1EWZqn&VBz8hg;j@gQwhJ3qK(;4n3__V#g<kweGiG>Uw%HQ&V4X
za7f~iWaQ-Pt%GK5!n@B<`1k~y&<o59IK3utJ}Z8|+y<PT4Rkkr?X&=FMkREf2H^K)
z|J+zDy3nSO^DiP!%{mKZP)uY%#M%-%z5*?l^oKJ8Hx!hWG2SQxp#5IEi{w=vo}6}6
zhz^w)m1R=u?@3Su`a13ALXzI0=$R`7Azd8G3aWS|%ekE%4o_=dbDIcEVSeD7+3w>+
zu$iHuXpq@<wA~sTB&}7c(a<7{ds^D#MlzQ9oDUg>ibY42INh9NMs2^t#NpMWc<&Ix
zpybrD;?yjPgCd(|4Nss&n=?6%dLxgm!@2Q3@~A?Z=Ck1jP=y1KX)Tc!^)?<55$cLW
zd#W)<D>0lr=WT!MEEGxv3c>y@3kSXry5i&bOJ=%?Z>oF%|F^mmc_TTb1g=aaJc)pe
zE0@dLr9#MQ15%Y{7%&JgE8CldB3WHFlro1j9JyTY-@qU)ZVbQ9sQ1Oep<<~}KB21n
zW81@9NJz*aKvA2psOwuzV54jQ*}I~mqRwWv?R`7|UM&<LFwfI?(e+DIKzk}EEQ~`&
z_F!`20IvVCq{Mxz>E5H@_fW*hh*wyKd-LkE%7^^zYpWes3iy5?>7lxHZ6l}uq{_`u
zRA-!Tq$Iu5M3hK1SHZ-@K_7mle{6mmI?mbeiQI5#>)nz*@>!lAG)%|H*Lze$Rjk}x
zVA^<6of8n$+Dd;gS%|GHhi_)St*)a3FC{?oLyiL$h^*vJI4<Q__<QYHot%JHB|ROL
zO9IKCgw`@>66jj$`LjZj7=r(Ji0J4TsVGPU*222Xvs&RIE$?^gKM$J4Jq9NPZwvJF
z$Q8C%AsJO7r_{}oh+2N|l9Z6~LZIuS1I?3zn9;mCvB!WB!$I*=CjrrTfS0}8hqd$r
z5|zTq&3t5Pv7}$!By)~m6DWl}YSV4pPM0w8nbuD3FRXI0k{F`Zh8!{)Ove1j#kdVI
zDe37;Dk_4zHv#)r*g|bs@2kGe5ZX!KT~r0V!!Ew3lGJF(0wpMi$Q53Go;0MC7m!hx
zkO@Pj(v|f4jce*---2D1G*_ug+5NXwq%kNl>&MOcwAI{b!`h5ZLX>35cWWujLSzSB
zBy1`GG8P4d86%tgZ<YGjIu|CFt{CU*a(-GC5jy@IK$8DXMJtG?o?4z}JNntlZPzLh
z=EQ#c3!-i7P5<vK098fa>US$Oqw`Tm_R+EZzk{TS05!dB8TAzWZ66EB(M8vX4;x-U
zs!HOR0x50H^jo^-9+IkOwJPGc)@jn$F!Vt+8*H<g^eVNLEE=aziM(pPh`q-lefsED
zN$04i@i*J>wDs%S#(_V?=uzYLfkuE?1&|)9<~|UgK<x;@ic2z&0q!GQfm7o94W|!6
zeG^7xCC!1*fd04FXJX(3vyjlz!o&m(F!5FKGI$;U+S}a~yt=v~f`52mb<eWI8yMw!
zk7ALsRp{P-G1zAw70pTa;RBI&0~{fo1j^dOJ+SPBk(#qg&cdP>vXzY67forwGB6sH
zcfFj9mhr{nn@nNrU^sc6;g4vYy@#Vx1es##d!tR}lB)ayk<{X3(F~Y-Mn~)LX*BHd
z4xD^8UiaQVy=OOgIC@SHfBPNpJi@PZ8Oj4xQ%j7DeZqvJMR2Y{EuR9u3rdWLAH>ln
zq7dhKU-%5qX!eSg>`kMHR_lY5647~D3!zBC81gBo2cuMx>f~<}uYP*_DOzlq@z%ym
zi*1qt9x}nW@L4S>E$t)>BnxL3GY;O*3ly^uR%XPfP(gOnGvsgs0<^TW45Cg*62M@g
zTw)OTQ*1e=6zzCM!WhT*T;hCYE)?1^BEaYOyzgVsO6$myCru{waAe}hg{kI$<OS`i
z(*D(y_3r&HkP~mB4R20R#feH278S>d8??lQt%tg%{N>9p?CjS*8I<57hY&-LpyO-)
znQ|WhRsz<ib^V{o=?LZ8$NH9MAc1U$GQ)dOg!o{t^&)1Bu<Q3U7YZ!2J0p(WY1&#}
zjQW39VgK5V?4X7Vqv}Zzxtmc2aA&zUr@L5=<?VMg_^-|knjb-ZTgCpg4+7O01{8lp
zNPd8@GCypr3xm*}u=)KS5B;sKvx|r0Aywx{H-T41epQ7S22%=gZH%anZPS<lHAm;#
za}2M_Y%=n96?#pve?!jh?noS*ti++Op*U`z`L?UbTQ*mDy&I3FatA)eKo0eoo;-hq
zLhuKwL!KCgJ*Zi(2y!bbi{n0@#{~c`bi`Bd`YIgfR)t@dI)GsGM6%$c*+eY~{9MF$
zuRot&_^A>E@m2$8V!}T(c%&bd{5wCgC98;1Rh_~!LJ)xK`v5Ay0N;k#w9&)>q=1Yh
zonC}t+Y=T)0rA5fPfAN`pVHwVXB`UC((s2StB(dp5w(F@h!ws*yv*aYyxh2PP9+{s
zBn8TDYFZJa&qIlsDF8wW@F@X}o>%lA!>&n#c6Myi3uXkY@W}q&P71=%kp%&}6&+xM
z@@6=ahi-7cBn}aABvq9&GaGBxx0;1>;6z=auWqy3WA*Y`<GVX!s2<1u-{Ln@lNC}%
zN<zZWsEZPlk8U(`RMz^2hHOb9Yv;l^<DWBs<PnMQ3+Za)iqnV`P+u<HOZ?RGG9(J?
zwSd^OAv+2pN}up1CR?05?`b^tR24oFmHO;zRMswX$l;qf#ek)LF?`J=b5>smUih><
z92*9964%JlkBv;Q8-_Z;C*-M=(p14aAKF8Mx*d=U2|}1d9tW7O$4f%oCaL?4o2|~V
z?+rvOnAiRK#j{4?a`#PnGKG{~F?PfQ@7K=>`WAdgftd&{*Ec?G>`|l4Bt@XO{EGHI
z=o5z1Gay5<a{BrBNl2j(_mvih{5yM3ut@>hcihM%3sij-w26ub;a0+aQ}QPSyk8nP
zq=cxs`?SMcC={n_EBwp%n_E<JP6qo8!g~T()PXw42i=M_GCg-lWXKZ7=T2;Z&x`fM
zuvYP9xXqP#vm4p5rIprLx?mIubFOSX8X~g_A{G~W?A1^OqTJVfP%T;i*E~AmJR8&(
zPybF^P3uk+1&H}}IRsy#`vPy|=gFPhxX*IJR!bAcjpIKi(eT@%d*;=CtAUDL4x@vB
zFLLx8B;d3{iY(|y(g}%W;q)F0<Z!X}w0M`1&w@te|0XIzMH;7m5!NR8K0~+K^l0%;
z{|}%Q;56Me_1x|&CNK2RsozQQ_|^~~aqu^Ug1>$5fIOj}I{Hlfs1hC9;Z!EAO-Fy<
zr(WrEIG>0Z8Vvyr1`R=+j$|F401ij|<GK}y#fhtOt%$j{##c9w&~Ci=ETcczKg*VX
zu`Mbt7LM=+6c!n=4akk`vGvQB{(%HG0U%;y%EJ~gfTy%>u8#-+`k)?C5HA_znvTXs
z0+Fh93{}oPF&c}k%@L^1NQV$CVatPBHb^v|@%(7FNL;xZu+KKM(&W0Qnj^Fd!Nr=%
z3mm;PsF}z_>%w)~kdU~zWTI}dR9sydd*A|cz;OiJ(;M-n-}yc4qk0|?sB#LFV||o|
zcZ;A)H)oeLTy!qci&$MRzfL6}Jj=NczEbnX6abh8><_*SO(D^Nf9-@X?@FmO<T|yi
zUVNguIa)=(+?2RK!PrS7Fn?LFO`MXCWh{knNfir0HeL(u+DcM8%wUaudU}F<a~a{+
z_zBp^({Hdh&1$e~RnpR;1hjJ4engNAQz`E68$E7o@i?9F_^pw<Z%EPaU&1`3BpIag
z>C@D6Hu)vHa;>FNa3psoyk`(EKeE001@LCFyI)xXT~$u1eC(O$;CcSk&3sgyt$=%E
zfb>ZSZwvotaxzBF+`Yvw)(`)9KAjd+>Z_Zg#Y(3A_Hs+MnQk`FH0jSDG8q`cA0cwo
z=Xq1wKX;=9*q^?s#`oQ1J=}YWcW0@t`g`NMZT_q-D1c#&HX!lpp>uKjT(i#I4-YW4
zy|qy-3zFn|*7npwB!KAKs?hA!=c4c*;u7J<>ZI9qw&Al!YD-V_k&@IZsRX?BNas7e
z+C5nTkZzUx`fD8ejPsuPKa!^Z;E<*uu@tyiG*9Q+b*`)%<nw&I+TCv@earin>xTl7
z1~_e~aP$}eWwnmu+qq8-Af)pZQrl0cCu@b`TK-%oszUhvhms`7Qr<YSfb%(cH0%Nw
znm9nR^ky`RqSoRcTOCu;fEFn@nC#+M%TOH~Hv!R8U!x2ol}uF><i7fe2*gJYUDAYs
z^s|A;=qlaW=@_octO6=Qad-3{qr2VXAgxJ+auq;7`agc2E<K|VNm0BB&2H&EHyn33
zl<{so{n+MTQfe_NaaBx0z8b@60hfE_o-1Gx=*vi&)tKKJ#MN&=!~^^Vr65Q;5{qTm
z(y~;i?CYg}ly}EuU<NQf)n399O7%nj8q2P!q(A5~q{dYA##HwopP)(na8Mt!>2yr^
zR5aavpYAyr{e9>hbQK`!^ssy6XZjVba!1xjkBBUbY~!*yq~-BFI0pFoEM*Dl&#Od>
z;3~l5Qr9yIE4y)EW)&kvur8ingahr5<RSxj0gS-(8-p{qhfCGs>BB0s-HtYI?|!1O
z4AJv!tX7<tSi#LOoZdsXE4jid*V@hw@DMZl*$1Wfp$tkUarw=Icvv*)XFqduD^e^;
z!C<-(0-`V9yxid4UJvnbevs;w6PcQtPT17F)zYF3esI<<)tVlg*|?5q{-fXV+9}!c
ztx-UILkc-dx(B3@M5kuxovlS5^U&tys{&ab`G9=!8IfilT5Cqmba3wfNbl!0f7@LO
zV0db4JV7=e6o2RPTs$y3Y#6CRCVsmQqQL@a_=k>8d_}bK!r(h&>m8iN#6iHSBEzDt
zcpyqx<HalB#0aoToCtnVITSzm=klw{tTU(4a>%7ov_0n>Ay*=GX&%}{R9Sd(bD6NF
zEK2O;fHVbN^uvF3xeruwI1*+UwU|Pq`Gpcc9sC{+Wmkd$Q<FHMRWzld`IBuZcmg><
z+DXpij+aa~b@Q&#l;@%EYvx*1LoU^i?(n+wDr;o0Dj$u=JNzdOV`%H^h&9E>lHRyp
zVKw88XJuOsw@>XG<KO2FMxB=J{-x%~NHrp%Tg6G5#i15bJ;D{t0n&QmY;|#`w&E1{
zD2sMydjr?20@`*B&Q7l_^E!N&a~hacMIINrNT>6-dwP126b(7fbDbVc#~mJ5WzXk)
zR;Q$`GNKWTYUWeTIyFxR`Mc?o%<wfn(vlg{?Hrje)c8mHY(0FR?>j#{E^Y6u_{MEK
zXIF;AF{bdoMj(6ap!Oqaq6Pa1qbo-ZLn4qqN(iIH3niXnyg!aGo@km*@vcB$+w*G~
z5r>+NtZeu}8k;*swc6!nu~KVM(YxtyyJOzF*=<6Ao2H##1GZ4N!I6@I3&psqJYE1Q
zZOrK2gW0T(ex9I@o9YjCQKmHgDl^xfcWJl2hNZjbpb_koau;}b=)EX-X$1E{R|rxr
z+6bLl#b=7I-`U%%|JKyiQ@r+UFmvSiB6w>2wv_`9%$DdM{s~-l20ZKq<qG-CF~k!a
zTg7})Yqpf5?$|7${C71c+v<A3pxAsTq=qy=NSJ7jw{ewg`mVW8@2i`fW?IT-d?~Wo
z;-6RMMUIr(JSumGC+wH?q9}oxbYSI>sn?CVQ<&Rts7e~V=aU^&#%(;((b(%H=Q^ck
z(`Tj2dBqp?FK%-bL%Fre2ILs^#2t@<7wyA%O;gDyaSei}gSyg-Wy;``tYO32qve0G
zNZ*vvvH3-jf;g76@3qcQDj+|Hj_C?aY^}SB)R8UV5LqlsF8h9&IWqFf^s4*%=cxU=
zqOGET1f$t~Jg1G2m+!RyF2S#nK~`z|JC4&!3)qtN6w~*QL*Y)&;y~r^q*US2ox*S|
zdTg$Wl2T3C*D5K|`Q_H#RB|Nin<qm02*Y0jb`MVeE-qZH4(u!}!?P(LI&QQzbt?Yx
zE>Y{&eEZP`&nX18o8IWS3H7174JIG_g{$+KL+Ol#VyQ+ITwc7+ZqR2`36@XFOKyBM
zuOC#6gFBsvQy>*LeaWGh!|;_NK-6H4IA7pSNHT1XWR;N?P$m{vk`AK=F;^e=`y;Qt
z-4h$PBZJB^=&jY^FoYO|QcKHAo6f^^UDI-jbCCjkHz2``#6`G11^9vSY+sJFHMQ!f
z1o7;V+qK5L9_-4k`TA`$#3HV4vJ=QR-d8U1eqtjdJ3>cCZ#!Ly-AE937p&oMau~h$
zbolNYbFQpN&Fx7O#QBc=&zM-=x#|gY9#K#7IiY5@bSlGnhyuRECSY(caL;XIV@wtL
zON_0PeC~erU-P+xo6Fo4)=i)az+b=z0%{?qT7bG%Ildw=;7U>9$ef{R6}^&%8{sth
zfve9oQv#e%S^Of`B}oOGKnHsxlQPb|S2oX9@wooK$FexCsKWVQwTzpOxagJ{0BYXg
zUjs%)f;I!vEZwi+AE>!J8S<h{$vl_BpjR=k=2b4wk=YivPm&SLdpFjqBT>BBcDiMM
z7wY4fRdjd%vK)1je=)zow4E#a>6B~bqLMA3E{)Qrj?<e@R_;LpAzO90#|ob;{~&JM
zv`-=9JlYkV`CKXALECnjQf3C7gloMWLp!nO6TW@uw=JoW5e!_Qmn4u)TC{u9MVN7D
zP^xAX253H4h17@24o+K%`2K4KKV4$4L9G|P@r_>23D?%E_FeZdY>BA9Jrus=eK=+c
zYVjaEIInSUTIq@IS1>d*9B}6Me{!?GKU?jtcUWEGademV3p*}mY15PgnrwE3FYWBu
zxoT;tZ{KozG7tpZUL3~nwD7*q?ww%goUE=WsC(I8TiaRlZyiiDSydx{C~JBV^M6u+
znt#I`VSWnV`5Ul5@iV7arP1_7wzOTZhFW5qn*KWBN*>7Jd1k=ndA3sW|Nmr=BgEs;
z6^2T?cmUQaJ)mZJ*&SMT?8q^#S^N@Mk-fFGH6<HMl=2dYGA0LD1!}&TekQJ9)vJwC
z&J}qi&N4M}9p!}!LXc7*7H8D5Tlkm%fc@DM4+J#8Tj=0Xv(RP4XH;Hr{z%hiIh;xW
zINV?U+V{2hV6t#oRR64LKUaw%%uV>*I~F|Ge$j=_6i|8*G5Sp@hs$zkJe51}?S%*M
zj`oHco958!pu^+lV3M#y+GOv=W42fPwCqt7tDa52{x?S-*ZBQ&SYk|RMKk!K;a7)Q
zZwyKNb~Vsir_u;hH#MI*ri-M;TO^mU?`wD4{hF^>Y}NaWj10r4`}1-2PgVBI^*go|
zO?_3)+kMUAfM!?9NK)=^iI8atk#X~-+V63k9u@E+*FzsFdmbqiE5IybZx7qa#717U
zLz_-K0u=F6{Of+)2Nd7NvKI|YXZzMYmhCURL}11^(UMeUeeINu${tHggbMraY?Ykf
zawu8g)Av~=_O@iGE%!>ED@#2M3hY|S+?Y|4<15#j4)5!|hdSGN?F4dumDjcrz}{Gn
zEnp(;3(J#AEiU+vO3ssS&SH_Vb1xwd!e(}d!u#GopR;~Juk)Y;hJmlQH}k2`USeaJ
z;-aG4gZWD8`Iq;CpZ;#RG!>AMlhX&jT=vJo;;kubZ*S+}=Wm$aph1z^+UN{z+U`%F
zr=x?Tmy6rrw_i&3ALX5&aa?J<gpTM<45fVF4Waq1@oD>g(S8jQQv5bE?e9-ov%kn4
zTh#@Nb61+(KUnBu<rcb&^`?QR?rwtjppNbMnj3YqHsK74Bz$)1&i(O}KC!)BK!Mql
z(#t}*A5t3w*HKX@$tfwFn@(<E!{AZS)9rC&Ucl3PTv`>J_6dyp{p^@gX9_;YwYX?}
zCbQe4*^b7yTOUyZSxI@7N2y~b1c76KLC<yTLW`#(g{V(595d9An@-ZYip}yk;6MQy
z$N8=5YXJTuEcE+>$<R_T*v$9->|Hf$=(Yd`DOcD#28P6k2QQb`+xeuImzSB!jyEBP
zvy~=!SeBRG^Ob{HG_(jqXh3CU7@<g*QlCD>I6d7Q{=VGq_mxpsPGLBlSgNxPB>LKM
zHT2<cOaUxE%l~SproK=Jdtd-2N_E(O7ZruWsWPHS4JN6Ic{fwiW#uL%B~8@X>bQt8
z3{;|@JzQ=pK0ZFO1Z{Z$o7);)kNH&7Ie?I+-a;)cS#@>w3l8<d$*7r`8KMaDhGkg4
zjUX8`XwlX8gM6~mSX>}S1tkQ6D@CV2JZn=Y$$_1ny1XKB>LGmMAxvUEap)Em6C=dL
z=bcaLu<`0DN|?3pU1r=tRVbg}KwC`z+A#Oo>Ww94D*pt%JWqS043Q(~3rjpXKi0c<
z?*Jf`9HH6e7{i(|xZX;2Kk~a0+UF~~r}H?YkI8<0>5_$$Ci4}BGU~}Wfsez+$)0Q8
z@(cC$%TX}(p!~%knQCcaWtB8Dqxl+19U$;u+9LNyGR%*|^uV5{OS8Mx!ymVYhKAHM
zHN$}LNKS-=gc(Soz-rd#Jfs)zrW05)RWo^_!y+Ra9_{AJbUO`6NX=1XdCIgu=(c(}
z3k4+t*c89`!6Hx$V#s9*l}n%!RWLF#GK&OYZs6taOTG{9G@$|Z5s_@*;^O}M%Gxa$
z9|{KN#K`kQ18i(;YDRq(CQ9Jk<K)aQZU>W?LM1dcG(v}lK7D%6X3b_?I2puAbVYF3
z+$<y`E8A(uha6Z52BSqsN9Q0NqL2PJg%7qVR_!H{#f8DT`(m?OScFiRpFa%-v97Tk
zVd?CY2oF;C4bdExNZ37`DDB*?Y)1f@DFM~2=rHVuEZV_@4&J=%79QsiKnJ6^ugTHO
z-`sV00py#>-0o8pwQ;@!_T-l$r8E}+iaCZu1Nr@`XC52?Tl6OJ>Zr7`enGYW_%;M5
z1Qz^tED_t${fvg{ZrjV%@GWJXZ+8S{uX$fwCyY=n2Hv|V+0%zTKk4OhC@Du*Ve+)l
z>Rsb=H1mFX9C+|}y>Gj{y{%|(&ju5S(Ec{W_<#=|K1e%4zl+13O<GzSz9i6h4X^?J
zfAjTRfp31N$?r8<X#ttr0u>tZwe(lNC#`7|@zzZ-GI69}%t1>UM37Zi`|<_v1oHCY
zC-S(HB!h!6P#w3Q7f5APndXMH0r$h2D!_UaltrG%I=oP2j{LFZgiBLfn~t45fwM1@
zh)vG|6LBK{4$q^>ASx~nq2IZrq5{jIH90XcsIO11r>Dn_48+dUy;@_1z2<*!$iu@E
z#2GO%qWs^mPnw~JF3t#yC=7}m8OjSyu1rsNr^|<9j@V(s*J4GrqkOR)y%;7io>#?j
zj!xuDN6#P2Iy-YxU?HQ>mIIrSB<#L^U4S&X?w^U1D+FGT^2?;|3iN#?mWm4I+@lNA
zECDYKT>FUxK^Ud#z~H$H5$awNB?luNg5FG6lB;VL1AXKF70z2t4~PG%WJW`$*eM0G
z5PdXY7P(t6A1bYEXF=#A?{^M9jewO|9BS%>NGhKwU+C+-ugKK^{i9>+rQGPE#8aki
zg>#vHLxr!Gmw**tmh)-dyp%dYRKY-&@*gVC6=(T}YRh4?apVW)BIn7;$%7@{8%F=z
z+gp@taWi8boydcOgT&i-YVr7(m>7YFTsjQJe{fHBDrHOL`oS3i$Gp{i<ahk+>}@w+
znpYjHT=zz3r~iBoT|Siyc*{t%Q&-3B>h8wH#}9?s-hBNA2UZpq7T48gcf=e{nosY4
zB_%EYJ5uAn-cAswqo;?*iiFqF(z3R(G0R_Y1%O1URI|b*vPL6dkb!Lujo6y!Npl`%
zy;=cmzuSEIudc2F+#|@H7=%5~WT0=a`Li~gmtGrA0vEG7#`&J-YYLw~+fP<4H#o{`
zYipAWp60|*k|>{I&fI<S_ZR*0<qHVR0=q}EYPLYtPfqRI-85rx$DaUz$=%5!Y`4nd
z;xLRV=gp7j$~(_lVhZeMsmRUa?vCeG7ux-Op^G(E=NX*lNcPAy0*b$$BErMNi~szg
z<KrW8cX$6YH8n*M*)2&=Pv4ZBOrY>9?CQ!Tse4m_KWo(BxY?sc)D%Xq$o@D|Y2j+<
zoJuLTgbyE*fBw$8|NZI-%wQyunrF8AKUa(T-o<_WX9Bd)?Ss|Vzx~^rGU^y1u9}*f
zP8ebr5crh>+1TdQK<KL%Z0m&)zW<Ojw~BjjYHFGZ^Ku(EhttfgtO(=R14Bb}umBae
z`+n5Gmx#`L@zD0*kdw!5ZJD8jOl)m=Q#m_3lW>>-02?r=4I17>tKNe+YsVDO(9#0s
z6R3FI!?1xl4okIvyDxjM{hUPE)z~?*9Em~uqglm-vYfj=<*8^bEiKbUefePQjp+<U
zHeYY|2PK<9f`C!+@qJ~wH5Uhk2?bW;xfZ)aDX7<bqorPVi&lm?mJ`7kv1)zvF?42T
zW=>l@s0IO#yUZUy_QA@v1j=87TI-(;uoCqbM@vf*1vP}f-x&uXA2ZRD&*3*S+W#G8
zFE1&9z)Hx)MK`wj{#ee1Pg_(}l*F-S?_5z;6|Vo&P4KEi<n&=!uURnbFs!<z^E*;`
z<;uf^28KXRuL~2m<2lT#sw#UaA{bs)UuI6nh+=y~Zja};JCUg|F#L=PCwm5~S!){-
zi<!|_iO{oEnAUvHMSJ@C{)6!1;^Kxk)wswt29^Jfy4(5MYxXxl0C@}&1O^T>7)VXx
zIuG5X3MVDh3H(c&W0+$YgoMbx)#$=d{|T$t4;Fgr5Q!3Z*60dIvi<Ykt1achVhxi@
zuE-#+l+)F3M}BT~0#m-MCR*u;+P^J<&^M>zX~8XV=%ec4?YwbdGkwJJVurG7#Im};
znbeSu*6i-37Z1#-GQjkhWD1n*5>o)Nsgu&uf(0yfyzbA1Gn6Uf&2@h@%<kFpdbd1y
zwL4rYJxDDc;3p8YCMYQA_vsTFjJ0ao+7Sy2U(-jw5fBi7l+r%-zzkP;S=k5$zk|Uh
z%)$ugL=?~3m`_bl;}R3k_r1a2!w|XN@YN2}q7*@F{?HAUY#dq6X8Kp;BbY6%m^Q7P
zV_@OX4#%NM<8eDsGiz8@hY1eM4C1=(kM+*{)!?)kNHmG)fkDUVaW}2-&mSb1a&-SQ
z?QR^+;`c;NdBqtD!o$a3?ite4c!k+6vNqp)IasNQiH+U7^?^m9<54l4wbzFiRftHW
zA=Kh)?D~x?D#wK)vaF2tWUY-HMnQN$a&qzptcwVpw8r3Cf%O73CT`M^SoUd~e0+Re
zyDqTZLh`(HZ89Jc@#3hise!4jNu8d$o*o*kEW?D!Zn++_SS6>+&~p_LlJ=e3a)_Yy
zc22+CeBx$WRlFDGqT`;Po;)yrH%;5v+4aE67gj=lXe@g>2{JYn0w63r{3q;u$sAzS
zK>S+`>1vB7Ost3qWIG$y1IR6y0T)llmD}TiZ`?>&6bpx>LF*ACFiY3Upa}CLI0!(h
z1Z2H(qH5MG80&_{bA)wn=P|i6l*=kA;(K~~o$KoAzUKonIa8NvtW-amB!Iyo+l`<?
z3~X$9Nl74q?E5=Mi9AjdNKm4?xw-kz)YQJyl_u`GTeB~&&yM4Y9<})`1<bI{A_wlk
zSg(Eg)Z=UU97<>>AZ^%v52BwNx#}RVt4rGKeoQp*11ka@Zu{(SaqgCp*JgMHthgYj
zvgvD678w4e=)Z*3l-jDQpVvNZ4BXuKMwP^fh=?%m%9xqa!L;(>>B;A<1N!QCe_Yui
zCaa#@3hQ2SdHL8Bf0=M)I9=^1?(FO!V3Pua-YKQhz;58t!Y-AXn;V~I93W`j53@DY
zH$I~O)v22sH?>cXD=P*Sw-Ki0q;9rbGeWiV>UjtW$acNCsI(04xw(_F1)SkvnPW^s
z{m<a6G`Z5o(7|3DzIS#Q)M6Byo13s_p0KHF$NwVmF<`CL+v<lpSD%`?dT>YxT(*!q
znyjp>eZ}kZefPH-S}ra;Oiav@k`f66gB7Ul`BG?B<JhaCXWcJLrPRci#2qG*IBy~+
zWeTP=6(vQfAc^Vp-WgCV06<pt-z)%vf#;G%rgAYURX71-J_ZicHsc5a*EFi46>|a!
zi4%Id4zmi$0Ae<RFoHRSxdrR8LN!(W+WBf*;Kpi2IP3CY+cd2HWi0-*FHg{3&wce@
zzkd$cr%);S@<V?+GxN#Bu?psCV7xVb{1Vv>tkV<y2$y&7!a7Vx_JfWDL#lFq8`hZ#
zcwaH_*)N4<W|I9+hvoqf_5a85Gvd-^Z<l#l37KbQ3)x9XR(AHv-Xmm(GP05+8A(P+
z2-)r@G@Q{TWnE>j|J(l_+{5_Z_detOe!X7L4?Bkuj4UkbmAKOKax&N`oAAxaXLK(|
zmtsek7<hjQJPslS+VF0%s<W&ysxk$W?FFeG`}{c>`fmZ!q8gW0IbF000)q9vPZ?!~
z*!>NfJPlqFeO!qvyGfO4&{>Mu>DPBy0Ct6*#DwunIFA&@%|uOKHmGD!Qc`kpaZwRx
z)wpx#!b|J+<mwx>q=+W>nV1hBKEyS#4qR5pt5z8aBHO-+T6N-<HMF!o#KuS2*ISLZ
z?CMrzNwKG9Xk+HE)Eu&&4+KgghK7fSVTAF6gcz6rT#uoTp86fH#~%w;`kz}sZQtT@
zK-)JfOH0f5eoR!SMv9fwjRYMyg-$UmKMd2lV*J2Jgb>Nhb?`S^&uLqECQ_W$Uq)8;
zm2QQ}JyD@#xT(6Am)KLiN2C=dLI@d}KzrNI4UT83CfzDuShbk2;g2C(-(*YJomlp7
zidW;|S61eNX)>prVo#-jH3H>F(p!fSHGPQ-y&oGJ8z%^Zc}rq9o9?r!GoF2InVP~P
zMpe=;jv8{3w2s(y6tQeXD(?;$ZcJPivpd||d<Gp2cx)xw2d^tN5+tvfc44F(Y-skL
zCmngLbO8+>$Usf>#2<go6drgvtFEtb`vWQ~ax6f;h}Kcms+krf0ucr2L=TuTQ=8mg
zXMitVQB6u(+Ht;1*N`uQB!)X9Z@czZdv9z~<U5*k@^JvC)2o60{{AY}lWM{_z5`{|
znI`f%x~IS=S61AiQ=_<%{e(Yp<C?0fsuRexV|3agh-PMSaXS1ysNFhjql<F7HK2Oq
zSUrr1H6eP~iG#T%jj>b`#aAenqiZM5I=2~;`RLK<N?g**m!dAOEgN6IE^TUJmX?;D
zhULAl#RqwGY<xT##?r-h-yxDn=M`BznpmW8*B%(?p@bt<$KJ0C;;2|}EGkJuO}k>k
zcvaHwJ$YQW9kc=#O<gCF!Y1j2^7~XE>!k(U^6BNRGqHB=4CwN47j}Kz#{MCxHeB5N
z%~P)soB1~QuhY?b<Z&p6KCXpa^%>%w(<@ccyuT(LjX~1yfXinBeva`FKoKUZs;ZFj
zYB5hT$^^|tn4y`(Y!3DZZH^W$J<=1-(cK(tQSbGdda7U0szqhgqxpI#l>2F}sqkc7
zCvm&Bwbe3kW3J1);plV>EotG-QTe!_S}E-VUBW}54%ykD?$_b_f&1Wy4gT`y=ui~%
ze|N`lxLAmB8oYGmv8sYOx-L@eC~9ibs13G*Pd5|-SGcEMSV$gDbslX_?QgAeFuO}j
zNyU-G0c7s2eQUe%47XfjR?U?xUSoFQtEa55pC9oH{wTLKaMiLal|$ASB>dCal4G#r
z744Z=t#Va6J02*k6wqNL<BTjmEH*zsKk>=kh)%Ah(c%+Vkz&`M5Ya%5q5I~SD~mZZ
z|FT?|?pg2k8*@Or7Rfq~p#RLahZ<E+K2(mp%o)0JN%3fB(Y(o>O~!pXYRWqFtXRhm
zFM!TH%@pQ&a1%8_Ml~#2OGoFls<G*;3!Ris7FeG+d#W1wH#}ZUBXD(f^?eUr!7xSJ
zx3tOX8)wMwyiLXE{M-y_wi5l?8OaqM9{zKk>)74ahwj7r_H8H+ZX_u_V*er_JcJ1f
z)xK)8YU5kCQbE3WrKOq7s+r&=d}f2i!9O0YK*54PP|_!n`?jFFY-vFd!KT4EWWL1q
z_8mkhfA!oJcNX`WReWap{K<%ldnB>ziWx?S7fpSVKl=v<)$Qy`H)fw-ul7FN3n+W?
z#G!{IzyC6@PJ4TM#(={Kxx&=B^`180UFU={Oo0s0?Jgrgo=;w^`uCI&XGyQIe@KXY
zm-x^D<&p7~Q=}wd{KcbvED`ihK@pvu$3UhuG70xI;&|zFvVhp4Mweb~EbWC|zT@X7
z`LOhGb;s{?%sbdk=&JoI9LId;!oEXB@rqu=b{&O%3|pUVS3@|KZ;m#I%my!=vugHM
zEm`c@4gz#<_FvATwM(y{U_g?~r=#WQ`E=;feO(R`rOnN(%gf84=&=5zT(#pabANXe
zG$%v`)v8SGmnSMFRJg^<{GY#kIr4pbzXdoej;>mdUmf&!(oGXI^W(>l{ovjf@}XNR
z1B{wVTPv?dnMFYO(5KHpar5iv$KfgsWAAQJ#FD`3=j7x(d+~yN!}d=ae*H>dpbTWO
zD-}r#LexGFhE6LOr5?SBqx1Lh@X*!LN`N7p7}1FgtOV|>XVF^I6!hCN2pDL9AnAH|
z)Yt=mbU2V{qg7Ls!m#KMkT&YP=hgmgZ7sB|cc?=ZOGrxgS@on8KY1Vd!<^u+^F%&|
zh@v0^zK6KBvDUV}kREx!cXCFSpnCVN?BmciITQ-T&7MjX)vJso9?VIODrTQ8!7!(E
zdA?B>&Ux=56@$?s36fV(C^Z%C5=UJ=KJe>e`@XcKUSqZx&tgqWC2a3ZTTAci=0+d%
zL1W_a$yw@!3um(V^<#SNRegLeJ+H2&qfVqt(h$dDweR<M@i)+@(t-~^l)jUzQt9C6
z2-^Z%3f*rH&Ts;R_0Yv9WvMyjBBi?_R65<FQEm+S2bq_aDxQ=-JLzALB5AzzgJqv_
z7b&|X&h)+?#30{;h|aRQI$Ab1wz@B?%|441XzD~U=9C$Y<&I$o2|}S#sM#ft&r(1A
z<w4;Zo0{I+*Y6->>du$<_i#K*?u!f!RrvMm7kLzuSJUT-iI}aeK($h%??H;3_Kioo
zmq9PQ2_Kmb{^WJ`0HDTi%cXVqrm*<z{&6(Dabxj{@}99txFX_UcQn%J(+dkfKgbb&
z#~X>t8Lym2ncdEVaYFoZxpSxcKAU7lZY~8V7dcfJX1GD*L{9#IR0$)RFahN-cmrcW
zMpk%GSRW{&()-A&OsQVY+y^2*SaDY719-i{iS_0#UTmJtbl1qj`1<<NrfT{8`cH_Q
zlmw>NV}A*)5U7Whg~SO0*V58bBIsuN$gPa6l_)9OTT4_CW0q4SA7j)ch?v(!`O6o9
z^ou{zgAWAf&j-8ra=kp?gg(4GG%%p5rA2X4@@s2rR*KU(4jNHuowgB1x7q^F_V)H}
z1SyIa48wEKLXtq-Bi-9{(dm^N+u?}(VxK2dzw4cb*<ajzLu~_S9IG$bZMSjrDiIH6
z$RsjImBvSc$azv(*}X`#{oLqEda)d0fKt76?G<j?caRGD2&jcx8!MG$!&-KB_7amS
zan}jVLp^@&3KMwd35PTWf@oPIQ~FBJfvdvgbl%`8SgPVtSv><xjxJB$AQUX|^PO6|
z?X_>yz!4@<X}$Kw>3!-~D@^Q$6sK$ds4Bf%ByuY$ox4_P5I?#^rfIB(VMkFUnTzDs
zl{A9JKFG~&5;d<=!B0+B2q4vfsCmiA$Z{dvSgwC<QMY%wTH*1f$qt5J!^Fh2H!@;y
z^tG;UvEI7Hx7SVLQJi0PY9wbNB?X1|&W0U~8YN^f7ucqng@JygMq{Injv~Gj2s{@r
zroIIhk<CRGaU;P2AZo09c|!xJ0Xd1@jS8d$Vn0{-KRWQ=1Y2<=QJy2|GbLBpTqq&u
zhG+Zn(Y~at`?Sg*V{U}1f(XSjfa0{fOa?|sF%Qa)QL{>`bO6nqa$Xi16JlJwIZFLb
z4N}S$kZo^&KYlc4z`@1C!%-)zu}CpEOJAd%ILNd?QUI7F$Ot*DH|?APccwNNZmCIC
z9N}z`x53l<Z0S_)FLdjJs4an?d9nvK+VF>-(|PiKKTsVz3n_LbmR45xb1vMVCcA0<
zY{bXP2}G{%$8Z4ywNVT=#H!taQ?P$RLP94;AvHJO7+G6WgB1c4*iJmr8FP`!(5<*#
zhIPr;0u$lQMDjz@bE@M<l7f|i3yr({_uyyY%q!;tvzJyD8QKDhSnNG-tO&}RlNA}G
z;^f3XQYc53Tvq2x(0w-fYt2D8b9UC+h%w&gV1Z=^Ab0%Jrvw#lL}K?PCudM5Rz0(@
zl=R2@`}?OfL8@7>lW;Q^6@sr^qvb`*n>Xd|Z58M_aasR<8$`6X<`)Wc!qBV2>cFME
zH4a;Bd(^XCU8-l5$sS;I=J$ebLa^y~kl<0`%!=h<q{>A1rDDhk`2_`!e*ADcg8=KU
zC^QgAq^w>fCe}GygY@8LXD{da_3FkXO5~nl5uJM<e+JS<|M2iB!vR|I=xhPQ1;Fnp
z%bEnR^Zyuh9#GLtG6a2LMNwdj3|6M=$<8o~(4vL)aSx?j>0g*#5Me&k<zwX;5D>85
z6-#+VqbyzdD5&oF^XIogn66FL+88DmNz&w=J9qA(!d+2h467E+#*_}NhCgFe6TTz-
zrZNQXUl6s_qCO0w!!L9F=OzuT3O5Eh*cg_@dqWoSz%r)<q?vNelgKktbveD7uT(x1
zO5;>YUj6b$;4L+P!?Wi-2#<PmdOPHxI7*1(pN7A7S-*wII!D>yLpua$U+>?%g<Ef3
z^FK5=4%UV3{;{)p;Ao*bI8<2(s0Zaru1bsUqa((eT0N0qMRh+Pdi(gl-<$SF2fJ9`
z?@069UeMywsozk5TAM~7cB!eU-B0QM;_fpr_3gyges_~%cgJ_MpW<7&((L)Ibq(vv
z*5bcBak@C{&%hA=;D_uu==^7bZ*-X;w4y41cJx!%6V7JTwD|q|w}O@*V^L9&_SLKE
z0Rhqgc2qTHDWLj`{h54fL>G*+N){&X(IXv{T<lgR@7EyjAM1Tt=dW&zMjkN`2!yfU
zo8DM-|L7=-inq5nZB0#$yN!*_{l(_}+x0Yvw|_ofxORDeMe>{9sFHw2D{O@=Yr<%2
z+5Yx=rB2q{VA$0zW8O1)xMkDdn;}_cf+S(VQ9pujUoKl%KQ<Dl)h1Ou@RUa(utg(J
zb}ps)F{DK%*wuM_kwK_<dcHI&WLi!cpP1m)`?-MUxN}TIoI{d>j+TtOjKNRIua3Kz
zK6I^y?{K*6bz+b|qC<Df0_vsddbODjK6Jf331}1(tQ$}1+JO}sN!z{iYQ`Oz*uCa(
zSS%m#vD1B~LB-H8eZp4!orA<_;mZ5>QBaEiwzqHZ{aZfa>lVMI<}Yu&C`qH=4gXyl
z?YlDi1go8-!3)mzu29a8mOAkyzYZ73@?E?La!vwK6$lf)5NrQO|JJ%TDqivEtfQ$r
zI`#80A<yR5$=Re@VG_U%^f5rE@j6epI7B`AKA%DY4-=(6E@P0;jmWp@QxiEwcU+j2
zC9&ca7LQ*I<g>_;qqC)EQmymCU;ekbX}9*ZMVu8CrW@xXm2Yb21I7-#<YUU$<A=LZ
zYn^ISc4<cBJpJ+K-2%n%y~fV6va&_5$o((>o&00HaHjUp|Nb+Tl98dBH51O+;*#_2
z!8&*`^SS4!XJH^)fA^IO2?-&$xGBYc@(1d=yX*hg^Xu2IL;lyELm@LIH~5psxFQdH
zg4$>8(lR4Yj(;Jq6K6x$OJBUun<E$$VtE7uA)WZ4onO}0a;1FDJeS-oFXQ>OUq(s_
zX_9(!o^iRD24o>^1>b3-oArH|M)|<o8x?M8C3>Udo2S5}+ot*{7HeJB(ZkKHtp-&0
z!A+Lv^d&*)WzbT@?D)Nu*NPXr)2|Xk%bBVcu9LXBx@Kr&Kz2(6b?;FGOJ|!$2;>bW
z)vtYx*x8ux07&s$fBSdj2>{(~R_zqN@CfwZsqndXBr@!&Y5uiBN01-Fhlhu+-FRWH
zN{vEAJ$2<7yeKTJ20`=vy!c6SY^tsv426_!klxq5-PNP?3f<?U0hq*1tD>Wmlo1b_
z%>W4xX7vHErp|xaWT-%v=EVFW4=!!3%*3|cU8gR1Gw+S<C*S$>ZF>41)RnU&N}W_i
zU;iw4g|05uNRdLHb$f{P@_S`Q&3cTErG345En<u!Hu^iswUyPL%skdQ1`_FGN#zm{
z1kGMD<E(&WRa)|xF7DRWRwHn&_%keOmAEc2TFLCPalr)7clU&>Lqb~LMW<bVhR5Of
zM4#8+MmUp{kYqryZ06<U1bmvBGPHQ@c3tHBc}@D1ULzYDJTIw3(lam7OoslCAJw>N
zX@l$^XJJFPM1(I~C^Zrk2de*Xa&mr2`B)A^e|n)PT95w~r=0J11Bo<W%sv^c$LW*v
zqx^jKp+}OGfah~q>d@`A-ru#IBP~{<gQe<;A8Iu5Eq=}dQ3i;h&B+^)iarY(;wT0s
z|DR()@7SP|>p||R%g4<n?tBu_9n!qUc&f@&erOr&<nd$9G{>3f)>iqYPKJ_RMlO9^
z(C?Zz-Ye6WzIKFjK!zT3d)=*K4$I&o#mmR1j`&l*HHtUnL<=Uzd|6EZ7=maI7N(sQ
zXC+FhlLbAkVq?RFP4n|c%l_CDr9LO`AG!H!@@tdFmt^?VN=|VlvZ~>vJij(~K|RVt
zrubn%F@kt@a*_cNw6m&8huF`TP768VWW~f;<Fv}7@#}p8#bNI)YCc6n>YC;H__<eW
zK`ie3IT<l0cuU%!CXe?XN;*@U=Q1@+^0>zhIH(yL)2+?6Ur=<8=sf1(4&GmV!xtoO
zyiSd9|JJ&xjL3k<@vOGi*$YhWc=Zi_jeYcCCW^@qL$2x_(OJxT!I?wsEFDMGA{L9L
zKF#U=0*Me55y1qfiCaewyaA0vD+U?O(co~ncwu73L6Uddx9`ZRXb)Rk?&^vP$ZV$;
zEgKaT6gVJnEt4ma&s{X|X|C-&`mBlb@0;&VtQ56qz{F5;Oi8G#3?)x2XdHaM%b8xO
zF{DnMqNj>i!AZTe=@j+GK{sL}4zSii^QY|S4Z_0@Ut<o_2aduDfDYZ<+!6+M1e4V;
z^j#T;kGQDJAePQcI*;^!ARFP$2t4?8qq70u8gSuNbMwOml6y~=?QG8?w8}Hjv9jK4
z5cxt}Ctte475?D+9YH881gBn!<twMNQ2i(J=zsUFdhOJi00v^Hl9JNcvj6GL{iMC6
zuKc{@&42$?Doo}#r)(0<s!ybiJ9_}&-0rpI$*ETs#!ljqJO>9y-}O~_|DPGbC)?i|
z@SFd5*)Wn<Qc^Nev*2yFyPMm><-MQf+JcO~(6^_ou3ft(rjNTOAtCXztc>@WsqhKS
zf~gmdyEs6|_B#f$^%G$Id+<sJMdClSzKz@6-My#5Yb2c0{g^C*2^ihC@y(k>oS|~K
z;LZh6(S$()X`j!~;r{k6cPd9}!*pk)@*RmsYpc`sx4+)WVL%v}nekgRx)_&Q)_kG~
zE?xhIjF|(sIfOpDb@AfG7*FTfx4~Jvf0qn1bt^2A{zWd}d#int@B+y9GoL<f-MV`9
zY8i=!J6x2n$3J+$Gu0Wszx9KMI{#etp+>EFy?wkjZ>F9TD}T_z4)Hgi=HS3y*w&0Q
z%Y(eU*hOr!m1sZVj~%^Wgj&js`-f|WO7Wm!<sBVICJhFOE5!miy3*w{#m7Z<tZ`~Q
zs?ln+acvMBLdo~jTiW-2RV#C{UnmOQTG^f5r!>2M-7yOzC(XwfYbwY{zCIM&#jRFK
z3wYn%@FLajVH;?`@uyp69|s4|<0=hD4Gj%PnVc)^ztH&nB(GfnsV~GToI<>|96n1;
zO%F)YtY~>GnwK(1aFNpY3#*Bt34EY?zV^j@0_NqRR(XAONeOo#q!J&oG#d+xDsWp#
zVPWAXz&;KJ2GsD*YxvI5!^8E+B@yP7Zv@`}@G9|ghpMd78&n8aaaM<lii-1wmv=D)
z-xkOq@Atwk-@SGX7hXHX!Qfbhih3w0zObKt(WXPO4Qe6psjILtH>_MS4=oaz<~RR7
zKItc=M!E6RmX?{Ye?N`SM0raTPD6!7IygAc$`sU;KYjWm)T5gpq37x<#FV6g@ohPv
zB=N`4&$A{Z1-Dy2ZEL&PJOJ*(54pZ5D@#q_^SEuJmN6p>ReV{r3U|o%+AW!Z?My^Q
zlB(ZZ(|MN!t{SD|gTK0n{Jp&p-1hEqWpPAEmZ|VC$ExDx5GM<nos;7fS7YZt-v@d!
z4$m)Ii0D+APFU77y>olb-fr!Mp+)w98Vw#AMe*x25V~oU{~a04RX=~8MNeK@Dd<Mp
zEl!2NXd0{LI^Gf){QUdZFRLA!G2N?Ip_`V?MYIHNUb`upmmM4&ET5#2<srrH71wR&
ziS%v(#51eJv0>A2Tv+cp<?Kzd?16g_Q{={78apd1``D=yEjS$}a5&f4E^$dT^-Vqg
zYmrjy1oK>i$+c_cz|Eh=#x~mfA{-shlW=E0UG{&ZmkXFC#foZ`OY=LY6!M?s$Qrr4
z50rUwVPPSg$Y6+vOu;VL&sK6nFI|o#?^6|BoK%IWa3pO9tgR?}ssw^p-TabCcKhy+
zf(qso0!%#A1FtaGd)%&dFO4YUiBkM`{N~sT-*(;1jGoBQzO(hCz$@$g`SYf?Z{J=4
z;xWk^WcpFw`mOD5c6@xiDb(w~Z+G!I{Q18wt|?1PGr7ICbdo9PCLSJd)<!xiC@AOv
zk{e|#961l?_a1UdDedCF;$2u(p$pY~7J5#v3#vSBJY-QiRw&GTU{Z*HhmNL!#!EHZ
GnEwIW+Z^ry
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..d13e099fcf3a7d705177d30cd644935564c7c2b3
GIT binary patch
literal 1307
zc$@(n1?2jPP)<h;3K|Lk000e1NJLTq001Ze000UI1^@s6#+n>Z00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H11ei%gK~y-6Wt3fLp5+<GfA{k|c|V@_O{<ArKwOQ8L7TQ)=`u?x
zL1aBq{9=lYpeH++$nZ$1v8GK}vMQSl209ob2-7M|JlT-?1(RB<){kgx{aCb3KhnnL
zZJNH%=Y8MT<-xm3(F?A#-vxgz{``!p0<i7n+D$=d#!)kf;xPJ&h;D!JxA_4sn*Af@
z#;7aY?Y*o6Oc<EYos;p+d-UHIbGvr!*cyb<q^KVRBrxH$E&cY%$;tf}b1xWkQ)tY%
zhd~2AglVrjI-<G{z^E!C|EmA3yWaD=bpspYm5s{~FPu3$&&=$>BzNi9i+5#z1$gh?
z@QJYA{KM5ZY>nepmxFkc)5kfo@4u-{m!G@-pYi~}#Kg`g>v8?}n>KA~^z`=PtmX8=
z0v~;NFv-h&_r%VrDS(&4@abx^dEffs;YN2~AKp2ZPM&1uop%zK=Z|mme$w3jZ1v_9
z%}ZVy-ZI>%R_X*2;hd$g8D;Kx`~A0)(iU4DeyvzlX*OS3f7eJOTG@w2F=o}Tk=a-A
zXOA=U>VMLr-M)M8z@xU&Y`%Qkt+zF5aRY;5ttHPhmfPof^NlysGB18}?H_mltlr)I
z!tgD(#MNpQ4|wNrML}7X9N4=zv01h?l9H)wt{!N7SNsoeb5IZm;*>7wrvLJ*;=^Bk
z@mLfEz5N?EG=laqq<s`srEC3z_;;Vcr02MD?V9+T4?Y+VqcGSwG}KT)yvI6=wI$YB
zyt7=l=DK+1)0rKiF+X3kc5Upx{g$jKP}NRAYtdSbs|E%dUr$d@M#VDMc2y0nbRIPV
z3MwETB1x%K`%IZIB%&+owGcUT1n^t{W$6T(E)mD8aiW9X3oBQwAW7R~d4`B!ZAo6_
z6lFoP*~Ezdl~=vA7DZ&UvlvxSMMUt<;f*2g??>U6QJIRQSsNE9YC6SLQB=UFkYx!j
zRRL7nErP*2iH65IU|4}HL$d^t7M;|XqR7F3!5}I)=O}H7bFM>lKvT9_XqI7AQ9wly
z5kwWgv;;up3o|o+^t++%D=Ik2f4EjrLKAW7cuQ=`y9VuBK6(7cFnI?1-rqZ%2own#
zRA?`sCNg{$1VPuS`BNKuSM{K(h$!AWtaBIxi;FEZ2oFZEy0x&duBnRVIjY)O5wu$6
z+~Oj@bl|N%^!3LJN#1q@u7<G{jH?i7Oxi9u_GK&cPRG5l>)71c#4azPMF~_OsG&g>
zZyj?dmQyE;iua?34<AahG{boZ1~7)e7|PNzJNr%MotyG7Hn+H#=0$<)>Oy;Zptl#T
z*Ks1upF5WTJB+F_JX#$yhDZ9>T^jdZb_ptQW?@N>9y*($Jn;78#d81$?lEJ=u>Fc&
z8jV`#iAez`7D-fj>iXw(JHYtF&L>S^?!U5sb==+EL{ZK!pJ(pKTqelW1<mX+<{u%9
z^g*-!|9+&vX-I&-Y*QUK7eu|~;mYQ)%Z#crWWZwB@2yVk`*V5dN21;m223byKrzPf
zi5LE~d5^ySQ&ESzMC2EM1VQkjE#<Kvi~4zE?g(L2U=Z*Dd}?9my{c~kd=Cf$xEQ#Z
RA`$=q002ovPDHLkV1iEcdMf|`
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..d3d10962c608b6c764637d7c8fe991664d8399e8
GIT binary patch
literal 2591
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
B1ZV&N
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..abf0ef4444212d1670d6e3fb2963e91259c1c3ed
GIT binary patch
literal 880
zc$@)n1CRWPP)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F800004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10_{meK~z|U#aGKt6hRa{x0ykMhSC3VhYJ&eD@kN0e_#v=Q5Qbq
zgAk3ze^3+IXc%B{!^TfA(Iq4^dl5)rx~h(gu4i}8Fc`?(nd-V-RejI7kLrhz#5g4z
zD>{Hie6rbTKQ&rEGKS5I0u+&cCfT#u*|}E$0Foeyot>S=di!f%Yt4%ktrgeX-%cEE
z6oK6ac|FUr3`*sI2m}KA_fnJ$K>`E;5(KRyZq7D0Z`YE<^wkysnRNE*Yrukwz>*M%
z2w=xhA!g^wW};)N1v#Y%BN7NC5^#}tc`b!uGMIowiok*oqxyg13GD6yxyr+g%O{8;
zm2`u!Lsn<Jd?Zmi;3V5n<tl&+K?sDvr_Ucp^ap6QT9~;x6MNysoq3!J0j2!X0-PX#
zy9*1E1=_CY$#f@xeudu^WiA1yLLd+D1PFmuAc(^pNCZ7luX@d7M5zHO$dyX=v%`-~
z1Yozfy%tE+)=Lp7V~Bik1Y8Xel#qh5czDKrxY;<4Hc3g*l4fuOw2MG|g#c?`)@o>^
zGIx6p&1TaRsd#UXhD6@4A+U!%TvO8^4M;#h&?f%Q{CwiO^Uxb++{c9&d2i>8F#}St
z2dL@X8wvJ(`PGmp>+v%p%{{$3Bn8G8{~&P^-w?|oO}vjiPaaeu0<;)Ln|cBOU}(Hi
z5lQk{=XtpZ%i!r&Em9Ddc9npQf{2uWQc1R5NOa<DxIu+EV@zVFHUhC%gC(w$I=uvL
zdqbT#7C#LY0CRY7P!v-1Qjz@JI7=Wb&DVgF#Qy$1vMlR6P>I>R|M2FCQu?9R`jQ))
zn!0klcyIA?@Ehp$y4cug=SrzFzdL4Gc04iBc>du2((A$?Ikc^;zU1G7Kfe9?{gZFs
ztsbtdytr}NwWG934b+K?Qm@y;R%i1lKk46EUVi-JtlrnjXap>H=bXVuCeZD6vAw-@
z?%*R6*zRl{<^A&q2S)fbn`};gzbJ5^v<=50WB)iEJAr>@fhjm6REYop0000<MNUMn
GLSTYmu7&^r
--- a/mail/themes/gnomestripe/mail/preferences/preferences.css
+++ b/mail/themes/gnomestripe/mail/preferences/preferences.css
@@ -44,16 +44,19 @@ radio[pane=paneGeneral] {
   list-style-image: url("chrome://messenger/skin/preferences/general.png");
 }
 radio[pane=paneDisplay] {
   list-style-image: url("chrome://messenger/skin/preferences/display.png");
 }
 radio[pane=paneCompose] {
   list-style-image: url("chrome://messenger/skin/preferences/composition.png");
 }
+radio[pane=paneChat] {
+  list-style-image: url("chrome://messenger/skin/preferences/chat.png"); /*FIXME*/
+}
 radio[pane=paneSecurity] {
   list-style-image: url("chrome://messenger/skin/preferences/security.png");
 }
 radio[pane=paneApplications] {
   list-style-image: url("chrome://messenger/skin/preferences/attachments.png");
 }
 radio[pane=paneAdvanced] {
   list-style-image: url("chrome://messenger/skin/preferences/advanced.png");
--- a/mail/themes/gnomestripe/mail/primaryToolbar.css
+++ b/mail/themes/gnomestripe/mail/primaryToolbar.css
@@ -265,16 +265,26 @@ toolbox[labelalign="end"] .toolbarbutton
 #button-goforward[disabled] {
   list-style-image: url("moz-icon://stock/gtk-go-forward-ltr?size=toolbar&state=disabled");
 }
 
 #button-goforward:-moz-locale-dir(rtl)[disabled] {
   list-style-image: url("moz-icon://stock/gtk-go-forward-rtl?size=toolbar&state=disabled");
 }
 
+#button-chat {
+  list-style-image: url("chrome://messenger/skin/icons/mail-toolbar.png");
+  -moz-image-region: rect(0px 360px 24px 338px);
+}
+
+#button-chat[disabled] {
+  list-style-image: url("chrome://messenger/skin/icons/mail-toolbar.png");
+  -moz-image-region: rect(24px 360px 48px 338px);
+}
+
 /* ::::: small primary toolbar buttons ::::: */
 
 toolbar[iconsize="small"] #button-getmsg {
   list-style-image: url("chrome://messenger/skin/icons/mail-toolbar-small.png");
   -moz-image-region: rect(0px 16px 16px 0px);
 }
 
 toolbar[iconsize="small"] #button-getmsg[disabled] {
@@ -467,16 +477,26 @@ toolbar[iconsize="small"] #button-archiv
   list-style-image: url("chrome://messenger/skin/icons/mail-toolbar-small.png");
   -moz-image-region: rect(0px 224px 16px 208px);
 }
 
 toolbar[iconsize="small"] #button-archive[disabled] {
   -moz-image-region: rect(16px 224px 32px 208px);
 }
 
+toolbar[iconsize="small"] #button-chat {
+  list-style-image: url("chrome://messenger/skin/icons/mail-toolbar-small.png");
+  -moz-image-region: rect(0px 240px 16px 224px);
+}
+
+toolbar[iconsize="small"] #button-chat[disabled] {
+  list-style-image: url("chrome://messenger/skin/icons/mail-toolbar-small.png");
+  -moz-image-region: rect(16px 240px 32px 224px);
+}
+
 /* ::::: end small primary toolbar buttons ::::: */
 
 #palette-box #qfb-show-filter-bar {
   list-style-image: url("moz-icon://stock/gtk-find?size=toolbar") !important;
 }
 
 /* Force the folder location and mail view items to fit in the available width
    in the Customize Toolbar dialog. */
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..f0f321f806a3e99d9683a42a76da0177dcd953c4
GIT binary patch
literal 679
zc$@*J0$BZtP)<h;3K|Lk000e1NJLTq001xm001xu1^@s6R|5Hm00004b3#c}2nYxW
zd<bNS00009a7bBm000A0000A00T2KrN&o-=8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10whU9K~!jg?U>7M6G0F~&vjx!9ylZf#A|~hBo<`D|9?fq3L#*F
z1TqkZrxYhH7Ci=!A22;t_IM=Ul_hsg)#>i)t}dAwZC0o00KD1&G_4Il(`r4y_A)*y
zX$v?44uKvp1bza2GaH2Zin<3Ri4^cX;4ZMWVCV(#+RVO%_>BONv<<ufb}O1(124_&
z-0Rz&1I5fgr+V;^R_9dGf!BXT67K@HYx;g#Oh9c#0PHwy_enWZTTKH%-qxE@1i%fa
zy(@+Y0PkJWcn-84;Fi;V-(wsBu;|9Es;<X40^o<!e&jJ;2Y_E5;|Kt6QTEYe90BkF
z_*&EU+{`|CoVx)mm9uYan!oY-k9v_^ke`fKz<00X5CAh90lBsPzycm4k#FMMWOVKQ
zu_6I-x5MWMr;z~1nd4n0#8@E!lI{afGR;mU9l3pb@~C(YJ_b%|eqscid0H*C0E)ox
zkY;_auJb|$ya(Qy*;RHdRRM~i5BS^naaECXbP4o<FQg;(x>8*K1CZ1O9sm!4o6aDs
zY6J{OM{VEChKmL+1d!AN9+76d$sVeaj@(yfb~*j$TmVVOq-NDRX-G==QuRACvyygz
zXRC@TLtW;?HE(O2u%xbafc7Mn-ryyG;-sn5CS0XTH~91l^jP27AFI>}oCffA*;b)a
zD{>kj`f^%^{<#9J0GMpaw-KPjM4W8|fQfG42B0BzW_}cE3&5Wh_zl|Ke%m+eE{p&G
N002ovPDHLkV1fki9`yhK
--- a/mail/themes/pinstripe/jar.mn
+++ b/mail/themes/pinstripe/jar.mn
@@ -11,16 +11,28 @@ classic.jar:
   skin/classic/messenger/dialogs.css                             (mail/dialogs.css)
   skin/classic/messenger/messenger.css                           (mail/messenger.css)
   skin/classic/messenger/primaryToolbar.css                      (mail/primaryToolbar.css)
   skin/classic/messenger/aboutSupport.css                        (../qute/mail/aboutSupport.css)
   skin/classic/messenger/accountCentral.css                      (mail/accountCentral.css)
   skin/classic/messenger/accountCreation.css                     (mail/accountCreation.css)
   skin/classic/messenger/accountManage.css                       (mail/accountManage.css)
   skin/classic/messenger/accountWizard.css                       (mail/accountWizard.css)
+* skin/classic/messenger/chat.css                                (mail/chat.css)
+  skin/classic/messenger/userIcon.png                            (mail/userIcon.png)
+* skin/classic/messenger/imAccounts.css                          (../../components/im/themes/imAccounts.css)
+* skin/classic/messenger/imAccountWizard.css                     (../../components/im/themes/imAccountWizard.css)
+  skin/classic/messenger/imBuddytooltip.css                      (../../components/im/themes/imBuddytooltip.css)
+  skin/classic/messenger/imMenulist.css                          (../../components/im/themes/imMenulist.css)
+* skin/classic/messenger/imRichlistbox.css                       (../../components/im/themes/imRichlistbox.css)
+  skin/classic/messenger/imStatus.css                            (../../components/im/themes/imStatus.css)
+  skin/classic/messenger/founder.png                             (../../components/im/themes/founder.png)
+  skin/classic/messenger/operator.png                            (../../components/im/themes/operator.png)
+  skin/classic/messenger/half-operator.png                       (../../components/im/themes/half-operator.png)
+  skin/classic/messenger/voice.png                               (../../components/im/themes/voice.png)
   skin/classic/messenger/browserRequest.css                      (mail/browserRequest.css)
   skin/classic/messenger/section_collapsed.png                   (mail/section_collapsed.png)
   skin/classic/messenger/section_expanded.png                    (mail/section_expanded.png)
   skin/classic/messenger/messageHeader.css                       (mail/messageHeader.css)
   skin/classic/messenger/messageWindow.css                       (mail/messageWindow.css)
   skin/classic/messenger/messageBody.css                         (mail/messageBody.css)
   skin/classic/messenger/webSearch.css                          (mail/webSearch.css)
   skin/classic/messenger/attachmentList.css                      (mail/attachmentList.css)
@@ -221,17 +233,21 @@ classic.jar:
   skin/classic/messenger/icons/zoomout-hover.png                 (mail/icons/zoomout-hover.png)
   skin/classic/messenger/icons/timeline.png                      (mail/icons/timeline.png)
   skin/classic/messenger/icons/timeline-inverted.png             (mail/icons/timeline-inverted.png)
   skin/classic/messenger/icons/empty-search-results.png          (mail/icons/empty-search-results.png)
   skin/classic/messenger/icons/black_pin.png                     (mail/icons/black_pin.png)
   skin/classic/messenger/icons/red_pin.png                       (mail/icons/red_pin.png)
   skin/classic/messenger/icons/tag1616.png                       (mail/icons/tag1616.png)
   skin/classic/messenger/icons/search-tab.png                    (mail/icons/search-tab.png)
-  skin/classic/messenger/icons/message-header-toolbar.png             (mail/icons/message-header-toolbar.png)
+  skin/classic/messenger/icons/message-header-toolbar.png        (mail/icons/message-header-toolbar.png)
+  skin/classic/messenger/icons/chat-toolbar.png                  (mail/icons/chat-toolbar.png)
+  skin/classic/messenger/icons/chat-toolbar-small.png            (mail/icons/chat-toolbar-small.png)
+  skin/classic/messenger/icons/status.png                        (mail/icons/status.png)
+  skin/classic/messenger/icons/status-small.png                  (mail/icons/status-small.png)
   skin/classic/messenger/icons/connecting.png                         (mail/icons/connecting.png)
   skin/classic/messenger/icons/loading.png                            (mail/icons/loading.png)
   skin/classic/messenger/tabs/alltabs-box-bkgnd-icon.png              (mail/tabs/alltabs-box-bkgnd-icon.png)
   skin/classic/messenger/tabs/alltabs-box-overflow-bkgnd-animate.png  (mail/tabs/alltabs-box-overflow-bkgnd-animate.png)
   skin/classic/messenger/tabs/newtab.png                              (mail/tabs/newtab.png)
   skin/classic/messenger/tabs/tab-arrow-end.png                       (mail/tabs/tab-arrow-end.png)
   skin/classic/messenger/tabs/tab-arrow-start.png                     (mail/tabs/tab-arrow-start.png)
   skin/classic/messenger/tabs/tab-bkgnd.png                           (mail/tabs/tab-bkgnd.png)
new file mode 100644
--- /dev/null
+++ b/mail/themes/pinstripe/mail/chat.css
@@ -0,0 +1,156 @@
+%include ../../../components/im/themes/chat.css
+
+/* Adaptation of #folderpane_splitter */
+#listSplitter {
+  background-image: none;
+  width: 3px;
+  min-width: 3px;
+}
+
+/* Adaptation of #threadpane-splitter */
+#contextSplitter {
+  background-image: url("chrome://messenger/skin/icons/vertical-threadpane-splitter-bg.gif");
+  background-repeat: repeat-y;
+  -moz-border-start: 1px solid #A6A6A6;
+  -moz-border-end: 1px solid #8C8C8C;
+}
+
+#contextSplitter[state="collapsed"] {
+  -moz-border-end: 0;
+}
+
+/* Adaptation of #folderPaneBox/#folderPaneTree from pinstripe/mail/mailWindow1.css */
+#listPaneBox {
+  border-right: 1px solid #8B8B8B;
+  -moz-margin-end: -3px !important;
+  background-color: #DEE4EA !important;
+}
+#listPaneBox:-moz-window-inactive {
+  background-color: #E8E8E8 !important;
+}
+
+#listPaneBox > * {
+  background: transparent !important;
+  -moz-appearance: none !important;
+  border: none;
+}
+
+/* Adaptation of #searchInput from pinstripe/mail/searchBox.css */
+/* it looks like appearance: searchfield gets us our search icon... */
+#IMSearchInput {
+  margin: 3px 4px 4px !important;
+  -moz-appearance: searchfield;
+  font: icon;
+  height: 22px !important;
+}
+
+#IMSearchInput > .textbox-input-box {
+  border: none;
+  padding-top: 1px;
+  height: 21px !important;
+  margin: 0 !important;
+}
+
+#IMSearchInput:-moz-lwtheme:not([focused="true"]) {
+  opacity: .9;
+}
+
+#button-add-buddy {
+  list-style-image: url("chrome://messenger/skin/icons/chat-toolbar.png");
+  -moz-image-region: rect(0px 32px 32px 0px);
+}
+
+#button-add-buddy[disabled] {
+  list-style-image: url("chrome://messenger/skin/icons/chat-toolbar.png");
+  -moz-image-region: rect(32px 32px 64px 0px);
+}
+
+#button-join-chat {
+  list-style-image: url("chrome://messenger/skin/icons/chat-toolbar.png");
+  -moz-image-region: rect(0px 64px 32px 32px);
+}
+
+#button-join-chat[disabled] {
+  list-style-image: url("chrome://messenger/skin/icons/chat-toolbar.png");
+  -moz-image-region: rect(32px 64px 64px 32px);
+}
+
+toolbar[iconsize="small"] #button-add-buddy {
+  list-style-image: url("chrome://messenger/skin/icons/chat-toolbar-small.png");
+  -moz-image-region: rect(0px 24px 24px 0px);
+}
+
+
+toolbar[iconsize="small"] #button-add-buddy[disabled] {
+  list-style-image: url("chrome://messenger/skin/icons/chat-toolbar-small.png");
+  -moz-image-region: rect(24px 24px 48px 0px);
+}
+
+toolbar[iconsize="small"] #button-join-chat {
+  list-style-image: url("chrome://messenger/skin/icons/chat-toolbar-small.png");
+  -moz-image-region: rect(0px 48px 24px 24px);
+}
+
+toolbar[iconsize="small"] #button-join-chat[disabled] {
+  list-style-image: url("chrome://messenger/skin/icons/chat-toolbar-small.png");
+  -moz-image-region: rect(24px 48px 48px 24px);
+}
+
+
+#statusTypeIcon[status="available"],
+#statusTypeAvailable,
+.statusTypeIcon[status="available"],
+#imStatusAvailable {
+  list-style-image: url("chrome://messenger/skin/icons/status.png");
+  -moz-image-region: rect(0px 16px 16px 0px);
+}
+
+#statusTypeIcon[status="idle"],
+.statusTypeIcon[status="idle"] {
+  list-style-image: url("chrome://messenger/skin/icons/status.png");
+  -moz-image-region: rect(0px 32px 16px 16px);
+}
+
+#statusTypeIcon[status="offline"],
+#statusTypeIcon[status="invisible"],
+#statusTypeOffline,
+.statusTypeIcon[status="offline"],
+.statusTypeIcon[status="invisible"],
+#imStatusOffline {
+  list-style-image: url("chrome://messenger/skin/icons/status.png");
+  -moz-image-region: rect(0px 48px 16px 32px);
+}
+
+#statusTypeIcon[status="unavailable"],
+#statusTypeIcon[status="away"],
+#statusTypeUnavailable,
+.statusTypeIcon[status="unavailable"],
+.statusTypeIcon[status="away"] {
+  list-style-image: url("chrome://messenger/skin/icons/status.png");
+  -moz-image-region: rect(0px 64px 16px 48px);
+}
+
+imconv, imcontact, imgroup {
+  padding-left: 4px;
+}
+
+:-moz-any(imconv, imcontact, imgroup)[selected] {
+  background: url("chrome://messenger/skin/icons/sidebar-item.png") 0 0 repeat-x #90A0C0;
+  color: HighlightText;
+}
+
+#contactlistbox:focus > :-moz-any(imconv, imcontact, imgroup)[selected] {
+  background-position: 0 -18px;
+}
+
+:-moz-any(imconv, imcontact, imgroup)[selected]:-moz-system-metric(mac-graphite-theme) {
+  background-position: 0 -36px;
+}
+
+#contactlistbox:focus > :-moz-any(imconv, imcontact, imgroup)[selected]:-moz-system-metric(mac-graphite-theme) {
+  background-position: 0 -54px;
+}
+
+:-moz-any(imconv, imcontact, imgroup)[selected]:-moz-window-inactive {
+  background-position: 0 -72px;
+}
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..57964f024ec073a2b1601a44d2254f905fe16f59
GIT binary patch
literal 3777
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ffd5b8bfc3df143f5da35586052761d05fdcd517
GIT binary patch
literal 4886
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
IM6N<$f?<XSPXGV_
index 71693f38c7fe44ca8684160da9f2222a84322d75..13ce5b1b867d63e4f7433448afc72d629e21eb6c
GIT binary patch
literal 53582
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
index ff47bed4a99507bcc0bae5503a01780b989419b5..168e2b68e95caa839691106b0d120a78595f22ba
GIT binary patch
literal 72383
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
T9(Y7s00000NkvXXu0mjf6XqN_
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..13b0fcfdd4b803bb42a4b1c69972a1d3219cba4b
GIT binary patch
literal 1320
zc$@(!1=sqCP)<h;3K|Lk000e1NJLTq001Ze000UI1^@s6#+n>Z00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H11f@wtK~y-6ZIoMVR8<s)zrE+2na)hp>68(HVwH;%A+{wI0}^RL
zNxgB=7cWs0ABet~1|>$ZRH}jUfPmm7F)<o%(HAvBV=%;`lqe>Z7Ahz=FR?(?Htm#l
z&dfPy@8!c3(g*kB%AbGz`Lp(?L<E2}Z^TzbVr;X<)&b~<K4FTq?tj|*mMQPhYW08{
z(QU@cLcm0{>I;nBw6aqUPt|SMu%Xd$oNd<HdO&Nfj~ip!cJJQZHC6YCQcV%Zc}c8Y
z1fZ4b4UK7kMC1s75)szD9q)+7qAja#Tbrz%z5)UI2M_Vh(as6q_x67IRPjZC?sbuO
zqlxl|7Or|QkzBAG2?H+ocX9I2=brJ49gX|!R)CEgH@3U3`&?5~Q)1q{c>tV0f1d8{
z?tHOWd}HU%ozDY&>bh-iEcU{Z_3IPU8XLh{h7TU(=!YMAwpi?V(3q`i_3LFToP_FJ
z)x12JPE;deuvWlmM!bu3bsd>7o~(ZMKp~STi@kSiQ**ga%>V@I*1)mP2><<)AHF}5
z4~@BFfA*PZBog_mxw*N#qM`y3!5D*xFg7+uS65d)2!gds_wLO*u6yvV_3M-F{Q2Pf
zs6qie5C8004(!|K8Ds8rB2hZ#FRV`H8D`uY1OY38HG&qGx!IY?bH5C9I9gZTc*FJO
zI`=n>UI531N8f}mT5(i_`LnN0ocrt27OgeeY_>cI0)~c$xN<^QLT0ns#F;Z^Hb)$%
zSXWn<baFYQw->BM3I)jLaebfqnwt2DKmKTQ#7gb7sw#5Di=ZqbrE*FwDyF4pao%32
zwMxZOm6b3$07`)q0|74o13)T09r^20gNUefI?cqy#MN*jg0&VAA(cvDtzCq*ekPqp
ze)|oy1`!AXl;<JEBALoc1a5Hv9GgUmgT<E82`CGQ##PDEH7g~`iNe|~(D-C&7W!ZU
z&=FKLPH78PDRs56D*;Ou3deB(EFu;W)c819JLwmK?}IU@%1V@oanN$2kTc7wuUW)+
zI9xJR0t%9P8bd=Ppyh<NCVe?n3sT+hz}K%%{y~9c6;w=TY^)#()dSe^@$og))z!-L
zysH64gtD?S1_uX4DfN>Q@kfV;?<=1*3mF+H*;xx=h^nYyWN1hPdK~NP*8X39?!15c
z<H@SBIe1|XfVfkMHNtnN-=DC)ZVj!Z&Yn5_al_pYCH3e9$d7^o$}^Z`hI9Kq@Iou?
zK@ixJCr_@bsi{e1G8uf|2f%e*f*|13sZ#}GOj`j=-|5pUugzu?(HS!!SK3P|g<mYv
z+tX7h!Yh-QiFG8RsSQi&8sl>^Hv{nd@X!2s{IHkzbFW>+%v$|c*)`WaantSh#uGE=
zBGxiKaF$;W@Av%NKU*62%Z^FRv_vA2?e+Eb@!7LyE3GvH0|V06*XIR6(7tQet{niM
zD%BEAB(^s!UL3EgtyKWS{r%G0)8qO1{HBLR_Nu9%)-h?fMjA?>R>y2nwf_&O?{T%-
zY*F<fp@yEoO6$r_`EjbQrKP3bb=@`*S*DZ%I2wlGwmp0H{4`bfkx~sY*etMga&**(
emmd>34)7nKscnGo9Kq870000<MNUMnLSTaIO?D;#
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c1e43c7c3a2c36e2566458240fba0aba0b140787
GIT binary patch
literal 2527
zc$@*>2_W`~P)<h;3K|Lk000e1NJLTq002M$000mO1^@s6rssJn00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H130z4;K~z|UeV2QTTvZ*%Kj+*#ckb-Gc6MiJZA-D`v04-mDF&3b
z3nWUkBpAVH2{Ak(Pz*I-ATiPiMiH=(@-i_HB;W%}h_<E(YF~(^*e<2J*jZazy1U&{
zcXwuY=g#9k&d)z)XFJ`N{*qtrWA5*F&hPX4o^vuvL};~Zewp~BW#k)nj2<IgtUwg}
zJBZk-mHz$4+bhGI#{PP(TBr=y$1SzO2$ujT*c}+zq9Zn}*er)m7r%P->bbF4>;~Jm
zdqiX@5Yt-k4uW8-*80_T>(=c*UHr>R%>{07rsD--8g>Oyv{m5MRU!vkzgJBF3Z+&)
z8^23g`r%7HbD4AQoX^?0R2L#pEFYtAWOvZJ?_c%EM0anx-GBa6gKu4_Zr4gbcGg8-
zbaLli5=(T<0Tk8ZN0j#Wh9B<Q?i-@}mv7`LW|IfjtXXrTZQIW*S+XR)c=2K<o6VxN
zW@2K3!NI|>x3|}iqG)Y<d;7YzYuB3D@xM{(Mr~Qobocbc=U#rflbSmhWm$N`!;JR!
zhJCNU?wc@NYed$q5-~FX6iQwDtN35qlJU#Fy6lG3-1K6y)&d-rL=gPQqZkY_GWh|U
z-`Y^FRqAj4@w>IJw+_DJTD>vux+^aD%Bqz6iF2TR0mR!t0W}Ybhp8VuK;Ij`D|?md
zKbLKkFSkOt_uhM7jK|~GU3Jw}sjjXrlF1~tZ37Sl0hLOHVzJ1UEnCVH6BB!ehK82E
z^2#ewGyauW?8SH@aov~hyfc;R?ncj<gK}K}WNHc-8KL&U2fY2<bLELrX-{a(@>L>{
z5)oED>#R;@-G_U6u1KX~c^ut_RyHCaMi3E<MASo%$zYW2+jf+z6@Nwv+g7UEk_q=0
zpa0U;X_cJ=@g!&)M4)>)6t+U>Lv0e78e{j{e=mF0`u)o`%F_UM-+lLWZEbBYUU}t}
zsdPGxWmzbt8k3jl@r}XveSF_%$BrH4$;rt_9)JAtho(D!eKMJR{_2}<O6dg)P^lDH
z7K9-x2p|lhRzpf97$0ZbYp<2ZJ@1ik8S{`@`J}r@Iqbb~*}~)*nRzHnHM#~MV2mJ!
zhKnF#_-O11|9NMqY(#(R`x~paW$FG)mz<kSbj@!RuJm*Q1K-;OPhJZ_t??{EZFG>G
z`wn|1FrV3Q_Ub9ub@yL<@x|@=e14{Qr4+`Pnc|yQfMT)8u3ft-K@cqc!jn&qT9)<S
z<;#|3vge))i3B)KV|d?3`99R^4WEAY4j$xh{r#07ik8}9nVaV=%*SI1#dvra6=|m7
zHxwXE0D>677$KgF(KWBb85tV?rh@o_IbC+b(J(#)S~DvF6o<gC&?M<<&AhIT_~8FW
zZ?-J!NIsu;lF1~V=b^Pesr$1bSeC`Yg$vyeKm71kU|_-g`A)X24H+J86mMCLIfNk+
zhR}pitD)+362@@eoH_2kk&#<%ZRr*5xr`k~bt3%6+*=fI5(E)!6xsH)b8KXMg>9*L
zHk*wT)EhFNodBTjK~S9mfEA-XowARPj;*k5doY*FwM9`x6h$+|H|<mqlu~3e8LhQm
z7PG8Rbai&R;&~0J+xGD$7}Izc1dZ-Dl^{ZAI<2jdk!7|Rb73}<#S4cqN=^v?k(T`c
zLh;F_J3zR^fRjw8p?nlfbZP(+9WUM(5T8`Ki^#}Dz?qp$hA<3g_QX~Nd>n*yI*l=A
ziHLNzr_-pZsmAxs?l%EM5vmDc8U(&icHTTfSYiW8OAKQKgD|TBCjywhDiJK=jkyY1
zS$3oN<==&#)h8yfoyKpb2|)mbYV7b@M6}i#V+_WaQ!3z85T^GD1!!#yR4OQAPF{l3
z5Kd4+5TF}*m94ej>y_)5#WPuE)qcE(N}9by5EM3*ave0gmBRHZwJY6t5_WBXzRj})
zAPopstOWpgRUfU?drGOHa=E-PlgVIN*2gMfHiT-m3Q$lY`zI$S7p5$$QT(X@G~a8D
z(i*XCrhFe;q+pxKZ1GCfOV7w=ElXJ~@}0J3@y-3f02)eDW1$Jy5*lfn^1LfL^O+a|
zgaIcl0XI8+FO7?Wsj*5FiEJ~*43$cymHB+$Jq7<0l+fy<qoV?B3q%ISCni?T@9J_8
zL1rmPi8K_@R6;C<0!p=-7}#b9s22|&KD6e{dCS{kjz%l{<R#4UnnWAw^?*ZzyZuP3
z4MuQ=4j;a6-ux?KjtX0jK#nJomSiXbEC%<yRhN(#eBYlK85#N6qD6~b*LBf)cKmS#
zH1DG*;=q9ewIB#y0FHeyH1zYc7c6j+)5qsYr%+P~)8^YYeLHv7LU=)MzN1#~1OLJI
z3-5cjiqmFykhJm*w;P_C@lI^zu=oAFo{6ISH{V`8bk(0_APW5-6$bWs#8c3ggJi3B
zK&k^$osjMVH^snz2R!4~AMDvIg{Pl>`e+!2clP)9drB!B#~~h%pWv?R&bVb+6bgk(
z7>4WDuV3H0N@O^SqPun$3LdVD%4SiSOvAHTRC_xrmqT@SLOu_v6nhRGszmkr`c)#m
z8er)$|IwqPN8at-`>t25l@SSXbb_RvBWdSwbOIrwQk`Pgo?YHp>Db#F@2Ef3B)WVP
zj~720-o1D4dlf&7KwIEupsfRvosh_a<q(7+{eACM#*Pm4Z&}Kt&G;vtc;cl}sq{u~
zZ?9LaRuK_mu^91qoJ1l)EEWTxR;$t1*XJEMa-@*WW*=zAe_P~*$<fg_xA*mV)gWjb
zh3Pcpa?sfcnGD2Y1S0Gl7^n;n4i+MKV0wu(<n`kpEB($H&JWy}`#|^N^WBbYUT0G|
zM1+ZQk@1PqXy3v9S`gIk+jK|pjL@P>;14$_>x>Hb*-re2XLWzpO?PzawsboRg*R2C
zTpW`_1AWz?UVD@>KUuLw!r4+^vu4e0j^nI5=bUrgTrOv)(`l4al*?s`#iH52e}AoB
zuRlCIJp9wv{{c<=tx|Vbj`Qe}?(RfqKCjd5?I^98DwQaXjhX&Jp&nMNk45m{Drt<b
z<it9-=2^EphTNnrdW_+-0;06qBav)Xk$(P9cU2Fbv`AZQ``6T`(cGd@S440=pls+5
zjQj(5Zp9|)J6-%e_uO-)G3FMnbq{bMU@N8iqA1!HMbWR<uV4TE>Ed5jYLSNT*w7Ox
pbpc>oB7G5TkKm<MBJZ_+|9?f8T1wFM1nmF-002ovPDHLkV1gs+&3^y@
index 59bab36087946a0170d4a1a6116d64472de66cdc..d10ecad9c101a530ff1036ee21d63c8cbdb7d135
GIT binary patch
literal 11489
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
E1NtGf#Q*>R
--- a/mail/themes/pinstripe/mail/preferences/preferences.css
+++ b/mail/themes/pinstripe/mail/preferences/preferences.css
@@ -74,16 +74,20 @@ radio[pane=paneGeneral] {
 radio[pane=paneDisplay] {
   -moz-image-region: rect(0px, 64px, 32px, 32px)
 }
 
 radio[pane=paneCompose] {
   -moz-image-region: rect(0px, 96px, 32px, 64px)
 }
 
+radio[pane=paneChat] {
+  -moz-image-region: rect(0px, 288px, 32px, 256px);
+}
+
 radio[pane=paneSecurity] {
   -moz-image-region: rect(0px 128px 32px 96px);
 }
 
 radio[pane=paneApplications] {
   -moz-image-region: rect(0px 160px 32px 128px);
 }
 
--- a/mail/themes/pinstripe/mail/primaryToolbar.css
+++ b/mail/themes/pinstripe/mail/primaryToolbar.css
@@ -412,16 +412,28 @@ toolbar[mode="text"] > .toolbarbutton-me
 #button-archive:hover:active {
   -moz-image-region: rect(32px 640px 64px 608px);
 }
 
 #button-archive[disabled] {
   -moz-image-region: rect(64px 640px 96px 608px) !important;
 }
 
+#button-chat {
+  list-style-image: url("chrome://messenger/skin/icons/mail-toolbar.png");
+  -moz-image-region: rect(0px 704px 32px 672px);
+}
+
+#button-chat:hover:active {
+  -moz-image-region: rect(32px 704px 64px 672px);
+}
+
+#button-chat[disabled] {
+  -moz-image-region: rect(64px 704px 32px 96px);
+}
 
 /* ::::: small primary toolbar buttons ::::: */
 
 toolbar[iconsize="small"] #button-getmsg {
   list-style-image: url("chrome://messenger/skin/icons/mail-toolbar-small.png");
   -moz-image-region: rect(0px 24px 24px 0px);
 }
 
@@ -688,16 +700,29 @@ toolbar[iconsize="small"] #button-archiv
 toolbar[iconsize="small"] #button-archive:hover:active {
   -moz-image-region: rect(24px 480px 48px 456px);
 }
 
 toolbar[iconsize="small"] #button-archive[disabled] {
   -moz-image-region: rect(48px 480px 72px 456px) !important;
 }
 
+toolbar[iconsize="small"] #button-chat {
+  list-style-image: url("chrome://messenger/skin/icons/mail-toolbar-small.png");
+  -moz-image-region: rect(0px 528px 24px 504px);
+}
+
+toolbar[iconsize="small"] #button-chat:hover:active {
+  -moz-image-region: rect(24px 528px 48px 504px);
+}
+
+toolbar[iconsize="small"] #button-chat[disabled] {
+  -moz-image-region: rect(48px 528px 72px 504px);
+}
+
 /* ::::: end small primary toolbar buttons ::::: */
 
 #palette-box #qfb-show-filter-bar {
   list-style-image: url("chrome://messenger/skin/icons/mail-toolbar.png");
   -moz-image-region: rect(0px 672px 32px 640px);
 }
 
 /* Force the folder location and mail view items to fit in the available width
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..f0f321f806a3e99d9683a42a76da0177dcd953c4
GIT binary patch
literal 679
zc$@*J0$BZtP)<h;3K|Lk000e1NJLTq001xm001xu1^@s6R|5Hm00004b3#c}2nYxW
zd<bNS00009a7bBm000A0000A00T2KrN&o-=8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10whU9K~!jg?U>7M6G0F~&vjx!9ylZf#A|~hBo<`D|9?fq3L#*F
z1TqkZrxYhH7Ci=!A22;t_IM=Ul_hsg)#>i)t}dAwZC0o00KD1&G_4Il(`r4y_A)*y
zX$v?44uKvp1bza2GaH2Zin<3Ri4^cX;4ZMWVCV(#+RVO%_>BONv<<ufb}O1(124_&
z-0Rz&1I5fgr+V;^R_9dGf!BXT67K@HYx;g#Oh9c#0PHwy_enWZTTKH%-qxE@1i%fa
zy(@+Y0PkJWcn-84;Fi;V-(wsBu;|9Es;<X40^o<!e&jJ;2Y_E5;|Kt6QTEYe90BkF
z_*&EU+{`|CoVx)mm9uYan!oY-k9v_^ke`fKz<00X5CAh90lBsPzycm4k#FMMWOVKQ
zu_6I-x5MWMr;z~1nd4n0#8@E!lI{afGR;mU9l3pb@~C(YJ_b%|eqscid0H*C0E)ox
zkY;_auJb|$ya(Qy*;RHdRRM~i5BS^naaECXbP4o<FQg;(x>8*K1CZ1O9sm!4o6aDs
zY6J{OM{VEChKmL+1d!AN9+76d$sVeaj@(yfb~*j$TmVVOq-NDRX-G==QuRACvyygz
zXRC@TLtW;?HE(O2u%xbafc7Mn-ryyG;-sn5CS0XTH~91l^jP27AFI>}oCffA*;b)a
zD{>kj`f^%^{<#9J0GMpaw-KPjM4W8|fQfG42B0BzW_}cE3&5Wh_zl|Ke%m+eE{p&G
N002ovPDHLkV1fki9`yhK
--- a/mail/themes/qute/jar.mn
+++ b/mail/themes/qute/jar.mn
@@ -48,16 +48,28 @@ classic.jar:
   skin/classic/messenger/featureConfigurators/folder-columns.png  (mail/featureConfigurators/folder-columns.png)
   skin/classic/messenger/featureConfigurators/toolbars.png    (mail/featureConfigurators/toolbars.png)
   skin/classic/messenger/primaryToolbar.css                   (mail/primaryToolbar.css)
   skin/classic/messenger/aboutSupport.css                     (mail/aboutSupport.css)
   skin/classic/messenger/accountCentral.css                   (mail/accountCentral.css)
   skin/classic/messenger/accountCreation.css                  (mail/accountCreation.css)
   skin/classic/messenger/accountManage.css                    (mail/accountManage.css)
   skin/classic/messenger/accountWizard.css                    (mail/accountWizard.css)
+* skin/classic/messenger/chat.css                             (mail/chat.css)
+  skin/classic/messenger/userIcon.png                         (mail/userIcon.png)
+* skin/classic/messenger/imAccounts.css                       (../../components/im/themes/imAccounts.css)
+* skin/classic/messenger/imAccountWizard.css                  (../../components/im/themes/imAccountWizard.css)
+  skin/classic/messenger/imBuddytooltip.css                   (../../components/im/themes/imBuddytooltip.css)
+  skin/classic/messenger/imMenulist.css                       (../../components/im/themes/imMenulist.css)
+* skin/classic/messenger/imRichlistbox.css                    (../../components/im/themes/imRichlistbox.css)
+  skin/classic/messenger/imStatus.css                         (../../components/im/themes/imStatus.css)
+  skin/classic/messenger/founder.png                          (../../components/im/themes/founder.png)
+  skin/classic/messenger/operator.png                         (../../components/im/themes/operator.png)
+  skin/classic/messenger/half-operator.png                    (../../components/im/themes/half-operator.png)
+  skin/classic/messenger/voice.png                            (../../components/im/themes/voice.png)
   skin/classic/messenger/browserRequest.css                   (mail/browserRequest.css)
   skin/classic/messenger/section_collapsed.png                (mail/section_collapsed.png)
   skin/classic/messenger/section_expanded.png                 (mail/section_expanded.png)
   skin/classic/messenger/messageHeader.css                    (mail/messageHeader.css)
   skin/classic/messenger/messageBody.css                      (mail/messageBody.css)
   skin/classic/messenger/webSearch.css                        (mail/webSearch.css)
   skin/classic/messenger/messageQuotes.css                    (mail/messageQuotes.css)
   skin/classic/messenger/messenger.css                        (mail/messenger.css)
@@ -127,16 +139,17 @@ classic.jar:
   skin/classic/messenger/preferences/general.png              (mail/preferences/general.png)
   skin/classic/messenger/preferences/display.png              (mail/preferences/display.png)
   skin/classic/messenger/preferences/composition.png          (mail/preferences/composition.png)
   skin/classic/messenger/preferences/security.png             (mail/preferences/security.png)
   skin/classic/messenger/preferences/attachments.png          (mail/preferences/attachments.png)
   skin/classic/messenger/preferences/applications.css         (mail/preferences/applications.css)
   skin/classic/messenger/preferences/saveFile.png             (mail/preferences/saveFile.png)
   skin/classic/messenger/preferences/advanced.png             (mail/preferences/advanced.png)
+  skin/classic/messenger/preferences/chat.png                 (mail/preferences/chat.png)
   skin/classic/messenger/preferences/background.png           (mail/preferences/background.png)
   skin/classic/messenger/preferences/hover.png                (mail/preferences/hover.png)
   skin/classic/messenger/preferences/selected.png             (mail/preferences/selected.png)
   skin/classic/messenger/preferences/auth-error.png           (mail/preferences/auth-error.png)
   skin/classic/messenger/smime/msgCompSMIMEOverlay.css        (mail/smime/msgCompSMIMEOverlay.css)
   skin/classic/messenger/smime/msgHdrViewSMIMEOverlay.css     (mail/smime/msgHdrViewSMIMEOverlay.css)
   skin/classic/messenger/smime/msgReadSMIMEOverlay.css        (mail/smime/msgReadSMIMEOverlay.css)
   skin/classic/messenger/smime/msgReadSecurityInfo.css        (mail/smime/msgReadSecurityInfo.css)
@@ -202,16 +215,20 @@ classic.jar:
   skin/classic/messenger/icons/tab-arrow-left.png             (mail/icons/tab-arrow-left.png)
   skin/classic/messenger/icons/tab-arrow-left-inverted.png    (mail/icons/tab-arrow-left-inverted.png)
   skin/classic/messenger/icons/mainwindow-dropdown-arrow.png  (mail/icons/mainwindow-dropdown-arrow.png)
   skin/classic/messenger/icons/mainwindow-dropdown-arrow-inverted.png  (mail/icons/mainwindow-dropdown-arrow-inverted.png)
   skin/classic/messenger/icons/tabDragIndicator.png           (mail/icons/tabDragIndicator.png)
   skin/classic/messenger/icons/tab.png                        (mail/icons/tab.png)
   skin/classic/messenger/icons/connecting.png                 (mail/icons/connecting.png)
   skin/classic/messenger/icons/loading.png                    (mail/icons/loading.png)
+  skin/classic/messenger/icons/chat-toolbar.png               (mail/icons/chat-toolbar.png)
+  skin/classic/messenger/icons/chat-toolbar-small.png         (mail/icons/chat-toolbar-small.png)
+  skin/classic/messenger/icons/status.png                     (mail/icons/status.png)
+  skin/classic/messenger/icons/status-small.png               (mail/icons/status-small.png)
   skin/classic/messenger/accountcentral/read-messages.png     (mail/accountcentral/read-messages.png)
   skin/classic/messenger/accountcentral/write-message.png     (mail/accountcentral/write-message.png)
   skin/classic/messenger/accountcentral/create-account.png    (mail/accountcentral/create-account.png)
   skin/classic/messenger/accountcentral/account-settings.png  (mail/accountcentral/account-settings.png)
   skin/classic/messenger/accountcentral/search-messages.png   (mail/accountcentral/search-messages.png)
   skin/classic/messenger/accountcentral/manage-filters.png    (mail/accountcentral/manage-filters.png)
   skin/classic/messenger/accountcentral/offline-settings.png  (mail/accountcentral/offline-settings.png)
   skin/classic/messenger/accountcentral/manage-imap.png       (mail/accountcentral/manage-imap.png)
@@ -280,16 +297,28 @@ classic.jar:
   skin/classic/aero/messenger/featureConfigurators/folder-columns.png  (mail/featureConfigurators/folder-columns.png)
   skin/classic/aero/messenger/featureConfigurators/toolbars.png    (mail/featureConfigurators/toolbars.png)
 * skin/classic/aero/messenger/primaryToolbar.css                   (mail/primaryToolbar-aero.css)
   skin/classic/aero/messenger/aboutSupport.css                     (mail/aboutSupport.css)
   skin/classic/aero/messenger/accountCentral.css                   (mail/accountCentral.css)
   skin/classic/aero/messenger/accountCreation.css                  (mail/accountCreation.css)
   skin/classic/aero/messenger/accountManage.css                    (mail/accountManage.css)
   skin/classic/aero/messenger/accountWizard.css                    (mail/accountWizard.css)
+* skin/classic/aero/messenger/chat.css                             (mail/chat-aero.css)
+  skin/classic/aero/messenger/userIcon.png                         (mail/userIcon.png)
+* skin/classic/aero/messenger/imAccounts.css                       (../../components/im/themes/imAccounts.css)
+* skin/classic/aero/messenger/imAccountWizard.css                  (../../components/im/themes/imAccountWizard.css)
+  skin/classic/aero/messenger/imBuddytooltip.css                   (../../components/im/themes/imBuddytooltip.css)
+  skin/classic/aero/messenger/imMenulist.css                       (../../components/im/themes/imMenulist.css)
+* skin/classic/aero/messenger/imRichlistbox.css                    (../../components/im/themes/imRichlistbox.css)
+  skin/classic/aero/messenger/imStatus.css                         (../../components/im/themes/imStatus.css)
+  skin/classic/aero/messenger/founder.png                          (../../components/im/themes/founder.png)
+  skin/classic/aero/messenger/operator.png                         (../../components/im/themes/operator.png)
+  skin/classic/aero/messenger/half-operator.png                    (../../components/im/themes/half-operator.png)
+  skin/classic/aero/messenger/voice.png                            (../../components/im/themes/voice.png)
   skin/classic/aero/messenger/section_collapsed.png                (mail/section_collapsed.png)
   skin/classic/aero/messenger/section_expanded.png                 (mail/section_expanded.png)
 * skin/classic/aero/messenger/messageHeader.css                    (mail/messageHeader-aero.css)
   skin/classic/aero/messenger/messageBody.css                      (mail/messageBody.css)
   skin/classic/aero/messenger/webSearch.css                        (mail/webSearch-aero.css)
   skin/classic/aero/messenger/messageQuotes.css                    (mail/messageQuotes.css)
 * skin/classic/aero/messenger/messenger.css                        (mail/messenger-aero.css)
   skin/classic/aero/messenger/attachmentList.css                   (mail/attachmentList.css)
@@ -359,16 +388,17 @@ classic.jar:
   skin/classic/aero/messenger/preferences/display.png              (mail/preferences/display-aero.png)
   skin/classic/aero/messenger/preferences/composition.png          (mail/preferences/composition-aero.png)
   skin/classic/aero/messenger/preferences/junk.png                 (mail/preferences/junk-aero.png)
   skin/classic/aero/messenger/preferences/security.png             (mail/preferences/security-aero.png)
   skin/classic/aero/messenger/preferences/attachments.png          (mail/preferences/attachments-aero.png)
   skin/classic/aero/messenger/preferences/applications.css         (mail/preferences/applications.css)
   skin/classic/aero/messenger/preferences/saveFile.png             (mail/preferences/saveFile.png)
   skin/classic/aero/messenger/preferences/advanced.png             (mail/preferences/advanced-aero.png)
+  skin/classic/aero/messenger/preferences/chat.png                 (mail/preferences/chat-aero.png)
   skin/classic/aero/messenger/preferences/background.png           (mail/preferences/background.png)
   skin/classic/aero/messenger/preferences/hover.png                (mail/preferences/hover.png)
   skin/classic/aero/messenger/preferences/selected.png             (mail/preferences/selected.png)
   skin/classic/aero/messenger/preferences/auth-error.png           (mail/preferences/auth-error.png)
   skin/classic/aero/messenger/smime/msgCompSMIMEOverlay.css        (mail/smime/msgCompSMIMEOverlay.css)
   skin/classic/aero/messenger/smime/msgHdrViewSMIMEOverlay.css     (mail/smime/msgHdrViewSMIMEOverlay.css)
   skin/classic/aero/messenger/smime/msgReadSMIMEOverlay.css        (mail/smime/msgReadSMIMEOverlay.css)
   skin/classic/aero/messenger/smime/msgReadSecurityInfo.css        (mail/smime/msgReadSecurityInfo.css)
@@ -465,16 +495,19 @@ classic.jar:
   skin/classic/aero/messenger/icons/arrow/foldercycler-arrow-right.png       (mail/icons/arrow/foldercycler-arrow-right.png)
   skin/classic/aero/messenger/icons/search-favorite.png            (mail/icons/search-favorite.png)
   skin/classic/aero/messenger/icons/xp-pin-grey.png                (mail/icons/xp-pin-grey.png)
   skin/classic/aero/messenger/icons/xp-pin-red.png                 (mail/icons/xp-pin-red.png)
   skin/classic/aero/messenger/icons/connecting.png                 (mail/icons/connecting.png)
   skin/classic/aero/messenger/icons/loading.png                    (mail/icons/loading.png)
   skin/classic/aero/messenger/tagbg.png                            (mail/tagbg.png)
   skin/classic/aero/messenger/icons/download.png                   (mail/icons/download.png)
+  skin/classic/aero/messenger/icons/chat-toolbar.png               (mail/icons/chat-toolbar-aero.png)
+  skin/classic/aero/messenger/icons/status.png                     (mail/icons/status-aero.png)
+  skin/classic/aero/messenger/icons/status-small.png               (mail/icons/status-small-aero.png)
 % skin messenger-newsblog classic/1.0 %skin/classic/aero/messenger-newsblog/ os=WINNT osversion>=6
   skin/classic/aero/messenger-newsblog/feed-subscriptions.css      (mail/newsblog/feed-subscriptions.css)
   skin/classic/aero/messenger-newsblog/icons/rss-feed.png          (mail/newsblog/rss-feed-aero.png)
   skin/classic/aero/messenger-newsblog/icons/server-rss.png        (mail/newsblog/server-rss-aero.png)
   skin/classic/aero/messenger/newmailaccount/accountProvisioner.css          (mail/newmailaccount/accountProvisioner-aero.css)
   skin/classic/aero/messenger/newmailaccount/search.gif                      (mail/newmailaccount/search.gif)
   skin/classic/aero/messenger/newmailaccount/spinner.gif                     (mail/newmailaccount/spinner.gif)
   skin/classic/aero/messenger/newmailaccount/success-addons.png              (mail/newmailaccount/success-addons.png)
new file mode 100644
--- /dev/null
+++ b/mail/themes/qute/mail/chat-aero.css
@@ -0,0 +1,21 @@
+%include chat.css
+
+#button-add-buddy {
+  list-style-image: url("chrome://messenger/skin/icons/chat-toolbar.png");
+  -moz-image-region: rect(0px 18px 18px 0px);
+}
+
+#button-join-buddy[disabled] {
+  list-style-image: url("chrome://messenger/skin/icons/chat-toolbar.png");
+  -moz-image-region: rect(18px 18px 36px 0px);
+}
+
+#button-join-chat {
+  list-style-image: url("chrome://messenger/skin/icons/chat-toolbar.png");
+  -moz-image-region: rect(0px 36px 18px 18px);
+}
+
+#button-join-chat[disabled] {
+  list-style-image: url("chrome://messenger/skin/icons/chat-toolbar.png");
+  -moz-image-region: rect(18px 36px 36px 18px);
+}
new file mode 100644
--- /dev/null
+++ b/mail/themes/qute/mail/chat.css
@@ -0,0 +1,206 @@
+%include ../../../components/im/themes/chat.css
+
+/* Adaptation of #folderpane_splitter -> #listSplitter, #threadpane-splitter -> #contextSplitter */
+#listSplitter, #contextSplitter {
+  -moz-appearance: none;
+  border-left: 1px solid ThreeDShadow;
+  /* splitter grip area */
+  width: 5px;
+  margin-top: 0;
+  /* make only the splitter border visible */
+  -moz-margin-end: -5px;
+  /* because of the negative margin needed to make the splitter visible */
+  position: relative;
+  z-index: 10;
+  -moz-transition: border-width .3s ease-in;
+}
+
+/* Adaptation from #folderTree */
+#listPaneBox {
+  -moz-appearance:  none;
+  background-color: -moz-Field;
+  color: -moz-FieldText;
+}
+
+#listPaneBox > * {
+  background: transparent !important;
+  -moz-appearance: none !important;
+  border: none;
+}
+
+/* Here comes some Aero styling */
+
+#conversationsDeck {
+  background: -moz-field;
+}
+
+#listPaneBox {
+  -moz-appearance: none;
+  background-color: rgb(238,243,250);
+}
+
+#contextPane {
+  background-color: -moz-field;
+  color: -moz-fieldtext;
+}
+
+#listSplitter, #contextSplitter {
+  border: none;
+  border-left: 1px solid #A9B7C9;
+  min-width: 0;
+  width: 5px;
+  background-color: transparent;
+  margin-top: 0;
+  -moz-margin-end: -5px;
+  position: relative;
+  z-index: 10;
+  -moz-transition: border-width .3s ease-in;
+}
+
+#IMSearchInput {
+  -moz-appearance: none;
+  background-clip: padding-box;
+  border: 1px solid ThreeDDarkShadow;
+  border-radius: 3.5px;
+  min-height: 24px;
+  margin-top: 1px;
+  margin-bottom: 1px;
+  -moz-padding-end: 2px;
+  -moz-padding-start: 2px;
+}
+
+.convBox {
+  background-color: rgb(233,239,245);
+}
+
+.conv-bottom {
+  background-color: rgb(233,239,245);
+  padding: 10px;
+}
+
+
+#conversationsDeck {
+  background-color: rgb(233,239,245);
+}
+
+.conv-messages {
+  border-top: 0px;
+}
+
+.conv-textbox {
+  -moz-appearance: none;
+  border: 1px solid #A9B7C9;
+}
+
+.conv-top-info {
+  background: transparent;
+}
+
+.userIcon {
+  border-width: 0px;
+}
+
+imconv[selected],
+imcontact[selected] {
+  background-color: Highlight;
+  color: HighlightText;
+}
+
+@media (-moz-windows-default-theme) {
+  imconv,
+  imcontact {
+    min-height: 1.7em;
+    color: -moz-FieldText !important;
+    border-radius: 3px;
+    border: 1px solid transparent;
+    background-color: none;
+  }
+
+  imconv[selected],
+  imcontact[selected],
+  imconv:hover,
+  imcontact:hover {
+    border: 1px solid Highlight;
+    background-image: -moz-linear-gradient(rgba(255,255,255,.98), rgba(255,255,255,.82)) !important;
+    background-repeat: no-repeat;
+    background-color: Highlight;
+    background-size: 100% 100%;
+    -moz-border-top-colors: Highlight rgba(255,255,255,.4) !important;
+    -moz-border-right-colors: Highlight rgba(255,255,255,.4) !important;
+    -moz-border-left-colors: Highlight rgba(255,255,255,.4) !important;
+    -moz-border-bottom-colors: Highlight rgba(255,255,255,.6) !important;
+  }
+}
+
+#button-add-buddy {
+  list-style-image: url("chrome://messenger/skin/icons/chat-toolbar.png");
+  -moz-image-region: rect(0px 22px 22px 0px);
+}
+
+#button-add-buddy[disabled] {
+  list-style-image: url("chrome://messenger/skin/icons/chat-toolbar.png");
+  -moz-image-region: rect(22px 22px 44px 0px);
+}
+
+#button-join-chat {
+  list-style-image: url("chrome://messenger/skin/icons/chat-toolbar.png");
+  -moz-image-region: rect(0px 44px 22px 22px);
+}
+
+#button-join-chat[disabled] {
+  list-style-image: url("chrome://messenger/skin/icons/chat-toolbar.png");
+  -moz-image-region: rect(22px 44px 44px 22px);
+}
+
+toolbar[iconsize="small"] #button-add-buddy {
+  list-style-image: url("chrome://messenger/skin/icons/chat-toolbar-small.png");
+  -moz-image-region: rect(0px 16px 16px 0px);
+}
+
+toolbar[iconsize="small"] #button-add-buddy[disabled] {
+  list-style-image: url("chrome://messenger/skin/icons/chat-toolbar-small.png");
+  -moz-image-region: rect(16px 16px 32px 0px);
+}
+
+toolbar[iconsize="small"] #button-join-chat {
+  list-style-image: url("chrome://messenger/skin/icons/chat-toolbar-small.png");
+  -moz-image-region: rect(0px 32px 16px 16px);
+}
+
+toolbar[iconsize="small"] #button-join-chat[disabled] {
+  list-style-image: url("chrome://messenger/skin/icons/chat-toolbar-small.png");
+  -moz-image-region: rect(16px 32px 32px 16px);
+}
+
+#statusTypeIcon[status="available"],
+#statusTypeAvailable,
+.statusTypeIcon[status="available"],
+#imStatusAvailable {
+  list-style-image: url("chrome://messenger/skin/icons/status.png");
+  -moz-image-region: rect(0px 16px 16px 0px);
+}
+
+#statusTypeIcon[status="idle"],
+.statusTypeIcon[status="idle"] {
+  list-style-image: url("chrome://messenger/skin/icons/status.png");
+  -moz-image-region: rect(0px 32px 16px 16px);
+}
+
+#statusTypeIcon[status="offline"],
+#statusTypeIcon[status="invisible"],
+#statusTypeOffline,
+.statusTypeIcon[status="offline"],
+.statusTypeIcon[status="invisible"],
+#imStatusOffline {
+  list-style-image: url("chrome://messenger/skin/icons/status.png");
+  -moz-image-region: rect(0px 48px 16px 32px);
+}
+
+#statusTypeIcon[status="unavailable"],
+#statusTypeIcon[status="away"],
+#statusTypeUnavailable,
+.statusTypeIcon[status="unavailable"],
+.statusTypeIcon[status="away"] {
+  list-style-image: url("chrome://messenger/skin/icons/status.png");
+  -moz-image-region: rect(0px 64px 16px 48px);
+}
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5887c7e154286558e9c90a29caf1a9736156f31f
GIT binary patch
literal 1349
zc$@)61-kl)P)<h;3K|Lk000e1NJLTq001Na000sQ1^@s6Gb4N`00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H11j0!~K~zYIrIuN2RYe%be>3NtId^YwOK%GZZNt)5pp<BEi72v#
z2x;8ngF<{FF+TZbOo>Eee1b<!Tw+29iAzkB7hI4SQ!$EAMUp~+LR+BAErD*>d(NCQ
zKHS@SZ@FL-zhsh=%*=Ow-}j&In-Rts=3xSu&q7fc|7GTG{_J~fvVaKSX0usUQ&Zys
zIba$H%B**Q)Y*$8m6jCpZcc;HgsYclZUN(fe<!Lj<%Zqe-My%_we`bfMa8g`R+vns
zMq6514s~{RE;Yv3bJmOT@$m)CZ4djIg^SI9ji$8^<%}`a#uzd8-{mr7_4W1DY}&l#
zcr;Pbcz@e^Wx^0?S=FP%LoYn@?DHFregExat*xztMVXREj{Usk=Eya>ad~su%$TB&
zE4}9x5Cg`5kRl1o0l6=}w0mzXnQo|CyhKG~aU56S=R7QFDIrANo>yMo_v?wy*MO;+
zO$uRKwuKvs(!8b>TPXmv=izxCp6}u1JO&5)k(LEe2gpKDw}A=3FAFp{oNZsYw9$>H
zQYhu-(-yOlwnat=_v)34?ZE5BKmfMm5Kko$LLh<TM~^T%GF&!ewTtfiZRR^&y}&#7
z*B^_38)YTaVOZ@(BRIAynQ&}{8;uYIVVeA*FP&G4SS&#h1cYJ8=*Tej%bLmp6<dFA
zj{>4lUNW7@)LsmHy@nVFk*{K73<wB)O{O+;3D9#lDy49vQGDMA0n)OF#S^S*TZfgG
z314eW5TLb2Yt7}J^9X4{UOvi|r`OO}|INh2Sk7^59HnrSLMer1S&WU{%+)tE93|fZ
zX3cjT9LFKh8sGEqJdNWxBvL6Tsv=2g1B%7tDCgh2%muOl>+QX798OoJyRTmx$f*L1
zqZC8eujZ<%(%tX8*>P}Y?Q9grXdy)Y0DB&OE{CHO@k9b$fb=~NUuy!bXJw7V;@GY`
z3s5YXe0Tr;<zIaE<)M+`p$9_~2n;kXTZ!ws7-Q(~J?{c-Tet7%%GB2V-v0E?m*48>
z=mEm#Hm)By_<n70>D-xo{)Ip(MI;i%^Sl!2O&Ah}AwdwJTo>gyvk;=_>WFQ4a_iS4
z+3coeO{?UBOa|MwXG#=f3_(Hn8>1s!?&%Jz(p4u;{B(Rf`D$~3wZ7K70XaD}Rr$oG
zExXn{@CZVPQXpdpi=cYWp61M{ll4G<afTw}Q<EDTSGLHini?$I#&#Sm%R&f25QYGR
z#!#8ArhY}U92~g(DBu==eBhL0+ou6}va72}NVzLFJ&i2*C<dB=3bc=q67mhQL^4ZY
z>|{k{zK;ljloH!<kWzy069xfN2!xcRQk4wl&tS2SnT~6=ZQMAtFP_jBy8pBR8dt7n
z_<BFLC&!s<k!aip{+<gY0CX-#4hVw0JUK6i5Q0z#=s@H9nn3&bo?o(Wju~M5mmhyv
zH<-<C+y2zf14GxYkV+&bKK|gnj#xB0X$r*_1i|!*hKBFRcj^)#UszVy*LzOR_XQ!P
zWhi%J7yJR;3u|g_0tc$n>05_C`RMU<I(-4~$^>Ab^ibPC64(Qz=0P+7d`>>HJDOy{
z1d!UiqrK~^PY>@%CX<&6pm%*+iFrQI{*T#!DuC#F!2tXNvmrQO4noI700000NkvXX
Hu0mjfqOo>W
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..6a9d32853b510bc844428f5794a3090485868594
GIT binary patch
literal 1792
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..17afe2394d056905da0417c291cd011d09277c76
GIT binary patch
literal 2812
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
O0000<MNUMnLSTZKKuT5s
index b184c51f1486a9daa0e1234b79f1a8c3394ae5a0..b6dca1afa1bc87b37f9cda0383ca194b3d5ec73b
GIT binary patch
literal 11630
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
index d33daa25ce46a0d75b010526cb87e6ea8bf10cd7..89e275e9f0cbad2de6cd1dd9961e40e4cc9b6f43
GIT binary patch
literal 22374
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
Bc2xiX
index aa3073ab9ef23dbf77add2f6a0ef9125d13d97a8..5e3635068ae1c2588e17b1f95d72a26bb45995ff
GIT binary patch
literal 53787
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
Z2i%HhY!qX!*~dcQ!Wi7pr|3Dw{14AAzg++T
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..116398315c6fe4220c6e75f1a962c0d4563e823d
GIT binary patch
literal 2472
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..872bdb49931af32b4143c606e01f807f776b532e
GIT binary patch
literal 1298
zc$@(e1?~EYP)<h;3K|Lk000e1NJLTq001Ze000UI1^@s6#+n>Z00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H11dmBXK~y-6Rg_zZUgZ^rpY?tFF!TSDzyzZ?YY2|aM4|+9V@ew<
zNyS=dm_#E43Z)=ks5dG&W6mc_uk3{&trkITS~WLKrJ#t1hzEi(iB)k(9CeJDNhWjp
z_x@+^@36c`l*xj1_rf~8>k(B2VB2dQn?+=Ys`iNxKt5H~Cl2?|zRR3A_n@p7Wxv8L
zKm<OC%8q5v>PK^RufF!m-3Yr;Rs#XyB+3(8`nMjNt2-*PA%O!5YXAgJc^JG$^=$xA
zRkpp_`NF(>!IpK`-_=;yTmiuOnKOJm{J(bF%{+B@YkM!iw-3l}NgI!Luie;87v2Cs
zJ$9DKPv0uFF7~Z>R-Xd+*T4VyOp>I3Sh{RUqpPb6fSJ||=SF@gy?2N1yZ8S60I!Q2
zw9V$jD>iLvHdn4hjG_4YYrcK+&C*XzJ$<+8fc*aXeB1m+_j7mNvZY~7GIuyCSMtV*
zSBhCzZanl}m8E&d(WM*uyG+&q!1n%*@Wp?Kvn@v6Ia0c+y7Ply{{6Zv&tB?V-P^R*
z0w5x&D&Bkk|Jmn-cYfo7KmI9CJ35Z`_V+h!qj6Q;&~Ed^kt2mG%X{pNcO^&G^sHT!
z<WOmgT3Xb!sI`TsA~8vJe)Rm3Uyp>}F6`-Dl{Y%Umk>(QJ9ZLIJcq;zv69YQys-T2
zqD}qFmoHtC<vCojk47cNK#UR4Z2aQ*%Eo_R|IM<#zEzza9Z(cMr?y%!J4=!z*~I8*
zPg2k5O4F#B)^pTaQ2PR}H9k5l86i(_1#2r$Gw-x$eF;K^_yT}9_6b5lN}!77wxTHJ
z<#|R3ffxg-5Mv|+kE&vfP?qJb4zf;F7#l-pXNg6DRu$1X)LJrQaIhw+OJypIpw@x)
zNP2nAq7Z_ki~-kHssLI|V%Kdax$U8!&tdWe^$tQC7ln4al-fB&3^uVOHpM0eBZ6}^
z-aFduQWVhfaZtsCfEaUm+p2`B!qqhjB(avWmo85CbgxJ;iC_{CffyiqXkGB?+Z?x6
zx@V@QZ_iHu4foZ{kX-hwbT*-`XpNN^jmKjQH%wfbxM}f{#iU6}mS%_;oUe%*nVOm=
zgmBV`WUcAx+vjz4Ve_1y{2D{Rd(U`PVMI<D?{wGM56eQMA_k0=%Z(cFvytKB?Y6J=
zK<#yKWO%ahK7vFfwU{hH(i9)y`{BvLN8RIncyefHsHIU65sa}|YY{Pc=Q%TUrtseH
za;k&hlw}bCYb|M(k!2Y+Nr=F3Sr$%pP*jy)4dnkwa_L{SzBzCHq6Ce~<oBL4?-xZ3
z{Q2EO)joiaw%dbZ<dN%p7dN`DnU4Z97pD2{%X6g!4y}7B><8GhcjxnInrvNt^UYn?
zUV9y?%IN4QpPfEkgczRQy>s6nz+r0-8hH4|?(XJ-E1!I+EE$@ZC?foITL=U46R3aj
zShhLIWgvR`5WF#*^tC>D>@W3IpgwlLY)BKk3yrs`O2o*C5W?>DFY8qR-m!CFbDE}m
zLI^!TwAOy^oO^uV-h*$=)x9XPAr(0guYlUXDW^KP{R*gm1Y0w6JC-Gdq5uE@07*qo
IM6N<$g5}C{^8f$<
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..d13e099fcf3a7d705177d30cd644935564c7c2b3
GIT binary patch
literal 1307
zc$@(n1?2jPP)<h;3K|Lk000e1NJLTq001Ze000UI1^@s6#+n>Z00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H11ei%gK~y-6Wt3fLp5+<GfA{k|c|V@_O{<ArKwOQ8L7TQ)=`u?x
zL1aBq{9=lYpeH++$nZ$1v8GK}vMQSl209ob2-7M|JlT-?1(RB<){kgx{aCb3KhnnL
zZJNH%=Y8MT<-xm3(F?A#-vxgz{``!p0<i7n+D$=d#!)kf;xPJ&h;D!JxA_4sn*Af@
z#;7aY?Y*o6Oc<EYos;p+d-UHIbGvr!*cyb<q^KVRBrxH$E&cY%$;tf}b1xWkQ)tY%
zhd~2AglVrjI-<G{z^E!C|EmA3yWaD=bpspYm5s{~FPu3$&&=$>BzNi9i+5#z1$gh?
z@QJYA{KM5ZY>nepmxFkc)5kfo@4u-{m!G@-pYi~}#Kg`g>v8?}n>KA~^z`=PtmX8=
z0v~;NFv-h&_r%VrDS(&4@abx^dEffs;YN2~AKp2ZPM&1uop%zK=Z|mme$w3jZ1v_9
z%}ZVy-ZI>%R_X*2;hd$g8D;Kx`~A0)(iU4DeyvzlX*OS3f7eJOTG@w2F=o}Tk=a-A
zXOA=U>VMLr-M)M8z@xU&Y`%Qkt+zF5aRY;5ttHPhmfPof^NlysGB18}?H_mltlr)I
z!tgD(#MNpQ4|wNrML}7X9N4=zv01h?l9H)wt{!N7SNsoeb5IZm;*>7wrvLJ*;=^Bk
z@mLfEz5N?EG=laqq<s`srEC3z_;;Vcr02MD?V9+T4?Y+VqcGSwG}KT)yvI6=wI$YB
zyt7=l=DK+1)0rKiF+X3kc5Upx{g$jKP}NRAYtdSbs|E%dUr$d@M#VDMc2y0nbRIPV
z3MwETB1x%K`%IZIB%&+owGcUT1n^t{W$6T(E)mD8aiW9X3oBQwAW7R~d4`B!ZAo6_
z6lFoP*~Ezdl~=vA7DZ&UvlvxSMMUt<;f*2g??>U6QJIRQSsNE9YC6SLQB=UFkYx!j
zRRL7nErP*2iH65IU|4}HL$d^t7M;|XqR7F3!5}I)=O}H7bFM>lKvT9_XqI7AQ9wly
z5kwWgv;;up3o|o+^t++%D=Ik2f4EjrLKAW7cuQ=`y9VuBK6(7cFnI?1-rqZ%2own#
zRA?`sCNg{$1VPuS`BNKuSM{K(h$!AWtaBIxi;FEZ2oFZEy0x&duBnRVIjY)O5wu$6
z+~Oj@bl|N%^!3LJN#1q@u7<G{jH?i7Oxi9u_GK&cPRG5l>)71c#4azPMF~_OsG&g>
zZyj?dmQyE;iua?34<AahG{boZ1~7)e7|PNzJNr%MotyG7Hn+H#=0$<)>Oy;Zptl#T
z*Ks1upF5WTJB+F_JX#$yhDZ9>T^jdZb_ptQW?@N>9y*($Jn;78#d81$?lEJ=u>Fc&
z8jV`#iAez`7D-fj>iXw(JHYtF&L>S^?!U5sb==+EL{ZK!pJ(pKTqelW1<mX+<{u%9
z^g*-!|9+&vX-I&-Y*QUK7eu|~;mYQ)%Z#crWWZwB@2yVk`*V5dN21;m223byKrzPf
zi5LE~d5^ySQ&ESzMC2EM1VQkjE#<Kvi~4zE?g(L2U=Z*Dd}?9my{c~kd=Cf$xEQ#Z
RA`$=q002ovPDHLkV1iEcdMf|`
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..d3d10962c608b6c764637d7c8fe991664d8399e8
GIT binary patch
literal 2591
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
B1ZV&N
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..61c24d5be427f1a71c6d5969dcd2f7229978dc9a
GIT binary patch
literal 648
zc$@)<0(bq1P)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F800004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10tHD#K~z|U%~re4+AtLU9Q->Bc^U?mN@VTK!gDaTKvj^a)F<Kc
z2HilSD>I{c0tTu!*dgvxM<#!KN!y73NS17$%lG-56URg;g=b3iRCEB;TpSLE&v~AI
zR7$<H1sFw<05DI}^a}tGU4q?iM*;lo_xmr5F%UvZCv#r61^}~^u6dSaI3AB8P16{7
zECE$YMT{}$3F^SB`!*W@=ytm(ilPMZ_JA!gA;bw3LMDhn+w%4axEd<t4)NJgr}mHR
z_xlht06>x?==FNR_ErK)sj`6vgF#TkX}>1&Py!*uKLxJZ*vfai-Ch&moI?nqY)*P@
zBjN(Dib;Ul6lS1m`hD5(Wx`9K3c6meoqzx^91g)43(AC*K$c|>j+(@1G<p?8>wG1v
zTMHpdg|h_O!T0kC(RrKjd_F@6QQK%uE*&3um4Gz^f75(EV7+H0VAjSA5PAvufVEf&
zNGZK1w~=Ym6H>}@Zr+4BA*8rouigu%Zn$hsxm+&jbUI}Uh-t7~E<Xt&KHTs3SnnlC
zavP7wUGwCebF5Y?A*F2mcBGX4qLePC)9F{|iS(FECPkKIN{`d&q~`PabvB#5^ZAC1
zMh_CsIX0Wkk37%cFBXgMO;2B&kq{%L6gcPDZnysx90+`pwFMqbpe^tq0&yIFYYQBx
ie1^}ExBo0}TYdwAFYK@)nSucT0000<MNUMnLSTZKFcJO$
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..abf0ef4444212d1670d6e3fb2963e91259c1c3ed
GIT binary patch
literal 880
zc$@)n1CRWPP)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F800004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10_{meK~z|U#aGKt6hRa{x0ykMhSC3VhYJ&eD@kN0e_#v=Q5Qbq
zgAk3ze^3+IXc%B{!^TfA(Iq4^dl5)rx~h(gu4i}8Fc`?(nd-V-RejI7kLrhz#5g4z
zD>{Hie6rbTKQ&rEGKS5I0u+&cCfT#u*|}E$0Foeyot>S=di!f%Yt4%ktrgeX-%cEE
z6oK6ac|FUr3`*sI2m}KA_fnJ$K>`E;5(KRyZq7D0Z`YE<^wkysnRNE*Yrukwz>*M%
z2w=xhA!g^wW};)N1v#Y%BN7NC5^#}tc`b!uGMIowiok*oqxyg13GD6yxyr+g%O{8;
zm2`u!Lsn<Jd?Zmi;3V5n<tl&+K?sDvr_Ucp^ap6QT9~;x6MNysoq3!J0j2!X0-PX#
zy9*1E1=_CY$#f@xeudu^WiA1yLLd+D1PFmuAc(^pNCZ7luX@d7M5zHO$dyX=v%`-~
z1Yozfy%tE+)=Lp7V~Bik1Y8Xel#qh5czDKrxY;<4Hc3g*l4fuOw2MG|g#c?`)@o>^
zGIx6p&1TaRsd#UXhD6@4A+U!%TvO8^4M;#h&?f%Q{CwiO^Uxb++{c9&d2i>8F#}St
z2dL@X8wvJ(`PGmp>+v%p%{{$3Bn8G8{~&P^-w?|oO}vjiPaaeu0<;)Ln|cBOU}(Hi
z5lQk{=XtpZ%i!r&Em9Ddc9npQf{2uWQc1R5NOa<DxIu+EV@zVFHUhC%gC(w$I=uvL
zdqbT#7C#LY0CRY7P!v-1Qjz@JI7=Wb&DVgF#Qy$1vMlR6P>I>R|M2FCQu?9R`jQ))
zn!0klcyIA?@Ehp$y4cug=SrzFzdL4Gc04iBc>du2((A$?Ikc^;zU1G7Kfe9?{gZFs
ztsbtdytr}NwWG934b+K?Qm@y;R%i1lKk46EUVi-JtlrnjXap>H=bXVuCeZD6vAw-@
z?%*R6*zRl{<^A&q2S)fbn`};gzbJ5^v<=50WB)iEJAr>@fhjm6REYop0000<MNUMn
GLSTYmu7&^r
--- a/mail/themes/qute/mail/preferences/preferences.css
+++ b/mail/themes/qute/mail/preferences/preferences.css
@@ -74,16 +74,19 @@ radio[pane=paneGeneral] {
   list-style-image: url("chrome://messenger/skin/preferences/general.png");
 }
 radio[pane=paneDisplay] {
   list-style-image: url("chrome://messenger/skin/preferences/display.png");
 }
 radio[pane=paneCompose] {
   list-style-image: url("chrome://messenger/skin/preferences/composition.png");
 }
+radio[pane=paneChat] {
+  list-style-image: url("chrome://messenger/skin/preferences/chat.png");
+}
 radio[pane=paneSecurity] {
   list-style-image: url("chrome://messenger/skin/preferences/security.png");
 }
 radio[pane=paneApplications] {
   list-style-image: url("chrome://messenger/skin/preferences/attachments.png");
 }
 radio[pane=paneAdvanced] {
   list-style-image: url("chrome://messenger/skin/preferences/advanced.png");
--- a/mail/themes/qute/mail/primaryToolbar-aero.css
+++ b/mail/themes/qute/mail/primaryToolbar-aero.css
@@ -478,16 +478,21 @@ toolbox[labelalign="end"] > toolbar[mode
   -moz-image-region: rect(0px, 342px, 18px, 324px);
 }
 
 #button-archive {
   list-style-image: url("chrome://messenger/skin/icons/mail-toolbar.png");
   -moz-image-region: rect(0px, 360px, 18px, 342px);
 }
 
+#button-chat {
+  list-style-image: url("chrome://messenger/skin/icons/mail-toolbar.png");
+  -moz-image-region: rect(0px, 396px, 18px, 378px);
+}
+
 #palette-box .toolbarbutton-1 {
   border: none;
   background: none;
   box-shadow: none;
 }
 
 #palette-box #qfb-show-filter-bar {
   list-style-image: url("chrome://messenger/skin/icons/mail-toolbar.png");
--- a/mail/themes/qute/mail/primaryToolbar.css
+++ b/mail/themes/qute/mail/primaryToolbar.css
@@ -371,16 +371,29 @@ toolbox[labelalign="end"] > toolbar[mode
 #button-archive:hover {
   -moz-image-region: rect(24px 480px 48px 456px);
 }
 
 #button-archive[disabled] {
   -moz-image-region: rect(48px 480px 72px 456px) !important;
 }
 
+#button-chat {
+  list-style-image: url("chrome://messenger/skin/icons/mail-toolbar.png");
+  -moz-image-region: rect(0px 528px 24px 504px);
+}
+
+#button-chat:hover {
+  -moz-image-region: rect(24px 528px 48px 504px);
+}
+
+#button-chat[disabled] {
+  -moz-image-region: rect(48px 528px 72px 504px) !important;
+}
+
 /* ::::: small primary toolbar buttons ::::: */
 
 toolbar[iconsize="small"] #button-getmsg {
   list-style-image: url("chrome://messenger/skin/icons/mail-toolbar-small.png");
   -moz-image-region: rect(0px 16px 16px 0px);
 }
 
 toolbar[iconsize="small"] #button-getmsg:hover {
@@ -670,16 +683,29 @@ toolbar[iconsize="small"] #button-archiv
 toolbar[iconsize="small"] #button-archive:hover {
   -moz-image-region: rect(16px 320px 32px 304px);
 }
 
 toolbar[iconsize="small"] #button-archive[disabled] {
   -moz-image-region: rect(32px 320px 48px 304px) !important;
 }
 
+toolbar[iconsize="small"] #button-chat {
+  list-style-image: url("chrome://messenger/skin/icons/mail-toolbar-small.png");
+  -moz-image-region: rect(0px 352px 16px 336px);
+}
+
+toolbar[iconsize="small"] #button-chat:hover {
+  -moz-image-region: rect(16px 352px 32px 336px);
+}
+
+toolbar[iconsize="small"] #button-chat[disabled] {
+  -moz-image-region: rect(32px 352px 48px 336px) !important;
+}
+
 /* ::::: end small primary toolbar buttons ::::: */
 
 /* ::::: toolbar buttons on tabbar toolbar ::::: */
 
 #tabbar-toolbar .toolbarbutton-1 {
   margin-top: 1px;
   margin-bottom: 1px;
   padding-top: 3px;
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..f0f321f806a3e99d9683a42a76da0177dcd953c4
GIT binary patch
literal 679
zc$@*J0$BZtP)<h;3K|Lk000e1NJLTq001xm001xu1^@s6R|5Hm00004b3#c}2nYxW
zd<bNS00009a7bBm000A0000A00T2KrN&o-=8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10whU9K~!jg?U>7M6G0F~&vjx!9ylZf#A|~hBo<`D|9?fq3L#*F
z1TqkZrxYhH7Ci=!A22;t_IM=Ul_hsg)#>i)t}dAwZC0o00KD1&G_4Il(`r4y_A)*y
zX$v?44uKvp1bza2GaH2Zin<3Ri4^cX;4ZMWVCV(#+RVO%_>BONv<<ufb}O1(124_&
z-0Rz&1I5fgr+V;^R_9dGf!BXT67K@HYx;g#Oh9c#0PHwy_enWZTTKH%-qxE@1i%fa
zy(@+Y0PkJWcn-84;Fi;V-(wsBu;|9Es;<X40^o<!e&jJ;2Y_E5;|Kt6QTEYe90BkF
z_*&EU+{`|CoVx)mm9uYan!oY-k9v_^ke`fKz<00X5CAh90lBsPzycm4k#FMMWOVKQ
zu_6I-x5MWMr;z~1nd4n0#8@E!lI{afGR;mU9l3pb@~C(YJ_b%|eqscid0H*C0E)ox
zkY;_auJb|$ya(Qy*;RHdRRM~i5BS^naaECXbP4o<FQg;(x>8*K1CZ1O9sm!4o6aDs
zY6J{OM{VEChKmL+1d!AN9+76d$sVeaj@(yfb~*j$TmVVOq-NDRX-G==QuRACvyygz
zXRC@TLtW;?HE(O2u%xbafc7Mn-ryyG;-sn5CS0XTH~91l^jP27AFI>}oCffA*;b)a
zD{>kj`f^%^{<#9J0GMpaw-KPjM4W8|fQfG42B0BzW_}cE3&5Wh_zl|Ke%m+eE{p&G
N002ovPDHLkV1fki9`yhK
--- a/mailnews/base/prefs/content/AccountManager.js
+++ b/mailnews/base/prefs/content/AccountManager.js
@@ -383,16 +383,22 @@ function onAddAccount() {
 function AddMailAccount()
 {
   let msgWindow = Components.classes["@mozilla.org/messenger/services/session;1"]
                             .getService(Components.interfaces.nsIMsgMailSession)
                             .topmostMsgWindow;
   NewMailAccount(msgWindow);
 }
 
+function AddIMAccount()
+{
+  window.openDialog("chrome://messenger/content/chat/imAccountWizard.xul",
+                    "", "chrome,modal,titlebar,centerscreen");
+}
+
 function onSetDefault(event) {
   if (event.target.getAttribute("disabled") == "true") return;
 
   Components.classes["@mozilla.org/messenger/account-manager;1"]
             .getService(Components.interfaces.nsIMsgAccountManager)
             .defaultAccount = currentAccount;
   setEnabled(document.getElementById("setDefaultButton"), false);
 }
@@ -626,16 +632,18 @@ function initAcountActionsButton(menupop
     document.getElementById("accountActionsDropdownSetDefault")
             .setAttribute("disabled", true);
     document.getElementById("accountActionsDropdownRemove")
             .setAttribute("disabled", true);
   }
 
   let prefBranch = Components.classes["@mozilla.org/preferences-service;1"]
                              .getService(Components.interfaces.nsIPrefBranch);
+  if (!prefBranch.getBoolPref("mail.chat.enabled"))
+    document.getElementById("accountActionsAddIMAccount").hidden = true;
 
   let children = menupopup.childNodes;
   for (let i = 0; i < children.length; i++) {
     let prefstring = children[i].getAttribute("prefstring");
     if (!prefstring)
       continue;
 
     if (prefBranch.prefIsLocked(prefstring) && prefBranch.getBoolPref(prefstring))
@@ -862,23 +870,23 @@ function restorePage(pageId, account)
 {
   if (!account)
     return;
 
   var accountValues = getValueArrayFor(account);
   if (!accountValues) 
     return;
 
+  if ("onPreInit" in top.frames["contentFrame"])
+    top.frames["contentFrame"].onPreInit(account, accountValues);
+
   var pageElements = getPageFormElements();
   if (!pageElements) 
     return;
 
-  if ("onPreInit" in top.frames["contentFrame"])
-    top.frames["contentFrame"].onPreInit(account, accountValues);
-
   // restore the value from the account
   for (var i=0; i<pageElements.length; i++) {
     if (pageElements[i].id) {
       var vals = pageElements[i].id.split(".");
       if (vals.length >= 2) {
         var type = vals[0];
         var slot = vals[1];
         // buttons are lockable, but don't have any data so we skip that part.
@@ -1111,42 +1119,45 @@ var gAccountTree = {
     }
     accounts.sort(sortAccounts);
 
     var mainTree = document.getElementById("account-tree-children");
     // Clear off all children...
     while (mainTree.firstChild)
       mainTree.removeChild(mainTree.firstChild);
 
+    var prefBranch = Components.classes["@mozilla.org/preferences-service;1"]
+                               .getService(Components.interfaces.nsIPrefBranch);
     for each (var account in accounts) {
       let server = account.incomingServer;
+
+      if (server.type == "im" && !prefBranch.getBoolPref("mail.chat.enabled"))
+        continue;
+
       // Create the top level tree-item
       var treeitem = document.createElement("treeitem");
       mainTree.appendChild(treeitem);
       var treerow = document.createElement("treerow");
       treeitem.appendChild(treerow);
       var treecell = document.createElement("treecell");
       treerow.appendChild(treecell);
       treecell.setAttribute("label", server.rootFolder.prettyName);
 
       // Now add our panels
-      var treekids = document.createElement("treechildren");
-      treeitem.appendChild(treekids);
-
       var panelsToKeep = [];
       var idents = mgr.GetIdentitiesForServer(server);
       if (idents.Count()) {
         panelsToKeep.push(panels[0]); // The server panel is valid
         panelsToKeep.push(panels[1]); // also the copies panel
         panelsToKeep.push(panels[4]); // and addresssing
       }
 
       // Everyone except news and RSS has a junk panel
       // XXX: unextensible!
-      if (server.type != "nntp" && server.type != "rss")
+      if (server.type != "nntp" && server.type != "rss" && server.type != "im")
         panelsToKeep.push(panels[5]);
 
       // Check offline/diskspace support level
       var offline = server.offlineSupportLevel;
       var diskspace = server.supportsDiskSpace;
       if (offline >= 10 && diskspace)
         panelsToKeep.push(panels[2]);
       else if (diskspace)
@@ -1157,46 +1168,48 @@ var gAccountTree = {
                              .getService(Ci.nsICategoryManager);
       const CATEGORY = "mailnews-accountmanager-extensions";
       var catEnum = catMan.enumerateCategory(CATEGORY);
       while (catEnum.hasMoreElements()) {
         var string = Components.interfaces.nsISupportsCString;
         var entryName = catEnum.getNext().QueryInterface(string).data;
         var svc = Components.classes[catMan.getCategoryEntry(CATEGORY, entryName)]
                             .getService(Ci.nsIMsgAccountManagerExtension);
-        if (svc.showPanel(server))
-{
+        if (svc.showPanel(server)) {
           var sbs = Components.classes["@mozilla.org/intl/stringbundle;1"]
                               .getService(Ci.nsIStringBundleService);
 
           let bundleName = "chrome://" + svc.chromePackageName +
                            "/locale/am-" + svc.name + ".properties";
           let bundle = sbs.createBundle(bundleName);
           let title = bundle.GetStringFromName("prefPanel-" + svc.name);
           panelsToKeep.push({string: title, src: "am-" + svc.name + ".xul"});
-  }
-}
-
-      for each (panel in panelsToKeep) {
-        var kidtreeitem = document.createElement("treeitem");
-        treekids.appendChild(kidtreeitem);
-        var kidtreerow = document.createElement("treerow");
-        kidtreeitem.appendChild(kidtreerow);
-        var kidtreecell = document.createElement("treecell");
-        kidtreerow.appendChild(kidtreecell);
-        kidtreecell.setAttribute("label", panel.string);
-        kidtreeitem.setAttribute("PageTag", panel.src);
-        kidtreeitem._account = account;
+        }
       }
 
+      if (panelsToKeep.length > 0) {
+        var treekids = document.createElement("treechildren");
+        treeitem.appendChild(treekids);
+        for each (panel in panelsToKeep) {
+          var kidtreeitem = document.createElement("treeitem");
+          treekids.appendChild(kidtreeitem);
+          var kidtreerow = document.createElement("treerow");
+          kidtreeitem.appendChild(kidtreerow);
+          var kidtreecell = document.createElement("treecell");
+          kidtreerow.appendChild(kidtreecell);
+          kidtreecell.setAttribute("label", panel.string);
+          kidtreeitem.setAttribute("PageTag", panel.src);
+          kidtreeitem._account = account;
+        }
+        treeitem.setAttribute("container", "true");
+        treeitem.setAttribute("open", "true");
+      }
       treeitem.setAttribute("PageTag", server ? server.accountManagerChrome
                                               : "am-main.xul");
       treeitem._account = account;
-      treeitem.setAttribute("container", "true");
-      treeitem.setAttribute("open", "true");
     }
 
     // Now add the outgoing server node
     var treeitem = document.createElement("treeitem");
     mainTree.appendChild(treeitem);
     var treerow = document.createElement("treerow");
     treeitem.appendChild(treerow);
     var treecell = document.createElement("treecell");
--- a/mailnews/base/prefs/content/AccountManager.xul
+++ b/mailnews/base/prefs/content/AccountManager.xul
@@ -37,16 +37,21 @@
               accesskey="&accountActionsButton.accesskey;">
         <menupopup id="accountActionsDropdown"
                    onpopupshowing="initAcountActionsButton(this)">
           <menuitem id="accountActionsAddMailAccount"
                     label="&addMailAccountButton.label;"
                     accesskey="&addMailAccountButton.accesskey;"
                     prefstring="mail.disable_new_account_addition"
                     oncommand="AddMailAccount(event); event.stopPropagation();"/>
+          <menuitem id="accountActionsAddIMAccount"
+                    label="&addIMAccountButton.label;"
+                    accesskey="&addIMAccountButton.accesskey;"
+                    prefstring="mail.disable_new_account_addition"
+                    oncommand="AddIMAccount(event); event.stopPropagation();"/>
           <menuitem id="accountActionsAddOtherAccount"
                     label="&addOtherAccountButton.label;"
                     accesskey="&addOtherAccountButton.accesskey;"
                     prefstring="mail.disable_new_account_addition"
                     oncommand="onAddAccount(event); event.stopPropagation();"/>
           <menuseparator id="accountActionsDropdownSep1"/>
           <menuitem id="accountActionsDropdownSetDefault"
                     label="&setDefaultButton.label;"
--- a/mailnews/base/util/errUtils.js
+++ b/mailnews/base/util/errUtils.js
@@ -115,16 +115,18 @@ Stringifier.prototype = {
     dump(s);
   },
 
   dumpEvent: function(event) {
     dump(this.eventAsString(event));
   },
 
   dumpException: function(exc, message) {
+    dump(exc + "\n");
+    Components.utils.reportError(exc);
     this._reset();
     if (message)
       this._append("Exception (" + message + ")\n");
 
     this._append("-- Exception object --\n");
     this._append(this.objectTreeAsString(exc));
     if (exc.stack) {
       this._append("-- Stack Trace --\n");
--- a/mailnews/extensions/mdn/src/mdn-service.js
+++ b/mailnews/extensions/mdn/src/mdn-service.js
@@ -39,19 +39,19 @@
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 function MDNService() {}
 
 MDNService.prototype = {
   name: "mdn",
   chromePackageName: "messenger",
   showPanel: function(server) {
-    // don't show the panel for news, rss, or local accounts
+    // don't show the panel for news, rss, im or local accounts
     return (server.type != "nntp" && server.type != "rss" &&
-            server.type != "none");
+            server.type != "im" && server.type != "none");
   },
 
   QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIMsgAccountManagerExtension]),
   classID: Components.ID("{e007d92e-1dd1-11b2-a61e-dc962c9b8571}"),
 };
 
 var components = [MDNService];
 const NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
--- a/mailnews/extensions/smime/src/smime-service.js
+++ b/mailnews/extensions/smime/src/smime-service.js
@@ -41,17 +41,17 @@ Components.utils.import("resource://gre/
 function SMIMEService() {}
 
 SMIMEService.prototype = {
   name: "smime",
   chromePackageName: "messenger",
   showPanel: function(server) {
     // don't show the panel for news, rss, or local accounts
     return (server.type != "nntp" && server.type != "rss" &&
-            server.type != "none");
+            server.type != "im" && server.type != "none");
   },
 
   QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIMsgAccountManagerExtension]),
   classID: Components.ID("{f2809796-1dd1-11b2-8c1b-8f15f007c699}"),
 };
 
 var components = [SMIMEService];
 const NSGetFactory = XPCOMUtils.generateNSGetFactory(components);